mirror of
https://github.com/plausible/analytics.git
synced 2024-12-31 21:42:55 +03:00
Rename metadata -> properties (#390)
This commit is contained in:
parent
73375b8289
commit
0b6e645b44
@ -7,7 +7,7 @@ function filterText(key, value, query) {
|
||||
if (key === "goal") {
|
||||
return <span className="inline-block max-w-sm truncate">Completed goal <b>{value}</b></span>
|
||||
}
|
||||
if (key === "meta") {
|
||||
if (key === "props") {
|
||||
const [metaKey, metaValue] = Object.entries(value)[0]
|
||||
const eventName = query.filters["goal"] ? query.filters["goal"] : 'event'
|
||||
return <span className="inline-block max-w-sm truncate">{eventName}.{metaKey} is <b>{metaValue}</b></span>
|
||||
@ -49,7 +49,7 @@ function filterText(key, value, query) {
|
||||
function renderFilter(history, [key, value], query) {
|
||||
function removeFilter() {
|
||||
let newQuery = removeQueryParam(location.search, key)
|
||||
if (key === 'goal') { newQuery = removeQueryParam(newQuery, 'meta') }
|
||||
if (key === 'goal') { newQuery = removeQueryParam(newQuery, 'props') }
|
||||
history.push({search: newQuery})
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ export function parseQuery(querystring, site) {
|
||||
to: q.get('to') ? parseUTCDate(q.get('to')) : undefined,
|
||||
filters: {
|
||||
'goal': q.get('goal'),
|
||||
'meta': JSON.parse(q.get('meta')),
|
||||
'props': JSON.parse(q.get('props')),
|
||||
'source': q.get('source'),
|
||||
'utm_medium': q.get('utm_medium'),
|
||||
'utm_source': q.get('utm_source'),
|
||||
|
@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'
|
||||
|
||||
import Bar from '../bar'
|
||||
import MoreLink from '../more-link'
|
||||
import MetaBreakdown from './meta-breakdown'
|
||||
import PropBreakdown from './prop-breakdown'
|
||||
import numberFormatter from '../../number-formatter'
|
||||
import * as api from '../../api'
|
||||
|
||||
@ -45,7 +45,7 @@ export default class Conversions extends React.Component {
|
||||
}
|
||||
|
||||
renderGoal(goal) {
|
||||
const renderMeta = this.props.query.filters['goal'] == goal.name && goal.meta_keys
|
||||
const renderProps = this.props.query.filters['goal'] == goal.name && goal.prop_names
|
||||
|
||||
return (
|
||||
<div className="my-2 text-sm" key={goal.name}>
|
||||
@ -59,7 +59,7 @@ export default class Conversions extends React.Component {
|
||||
<span className="font-medium inline-block w-20 text-right">{numberFormatter(goal.total_count)}</span>
|
||||
</div>
|
||||
</div>
|
||||
{ renderMeta && <MetaBreakdown site={this.props.site} query={this.props.query} goal={goal} /> }
|
||||
{ renderProps && <PropBreakdown site={this.props.site} query={this.props.query} goal={goal} /> }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -5,39 +5,39 @@ import Bar from '../bar'
|
||||
import numberFormatter from '../../number-formatter'
|
||||
import * as api from '../../api'
|
||||
|
||||
export default class MetaBreakdown extends React.Component {
|
||||
export default class PropertyBreakdown extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
let metaKey = props.goal.meta_keys[0]
|
||||
this.storageKey = 'goalMetaTab__' + props.site.domain + props.goal.name
|
||||
let propKey = props.goal.prop_names[0]
|
||||
this.storageKey = 'goalPropTab__' + props.site.domain + props.goal.name
|
||||
const storedKey = window.localStorage[this.storageKey]
|
||||
if (props.goal.meta_keys.includes(storedKey)) {
|
||||
metaKey = storedKey
|
||||
if (props.goal.prop_names.includes(storedKey)) {
|
||||
propKey = storedKey
|
||||
}
|
||||
if (props.query.filters['meta']) {
|
||||
metaKey = Object.keys(props.query.filters['meta'])[0]
|
||||
if (props.query.filters['props']) {
|
||||
propKey = Object.keys(props.query.filters['props'])[0]
|
||||
}
|
||||
|
||||
this.state = {
|
||||
loading: true,
|
||||
metaKey: metaKey
|
||||
propKey: propKey
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchMetaBreakdown()
|
||||
this.fetchPropBreakdown()
|
||||
}
|
||||
|
||||
fetchMetaBreakdown() {
|
||||
fetchPropBreakdown() {
|
||||
if (this.props.query.filters['goal']) {
|
||||
api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/meta-breakdown/${encodeURIComponent(this.state.metaKey)}`, this.props.query)
|
||||
api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/property/${encodeURIComponent(this.state.propKey)}`, this.props.query)
|
||||
.then((res) => this.setState({loading: false, breakdown: res}))
|
||||
}
|
||||
}
|
||||
|
||||
renderMetadataValue(value) {
|
||||
renderPropValue(value) {
|
||||
const query = new URLSearchParams(window.location.search)
|
||||
query.set('meta', JSON.stringify({[this.state.metaKey]: value.name}))
|
||||
query.set('props', JSON.stringify({[this.state.propKey]: value.name}))
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between my-2" key={value.name}>
|
||||
@ -55,26 +55,26 @@ export default class MetaBreakdown extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
changeMetaKey(newKey) {
|
||||
changePropKey(newKey) {
|
||||
window.localStorage[this.storageKey] = newKey
|
||||
this.setState({metaKey: newKey, loading: true}, this.fetchMetaBreakdown)
|
||||
this.setState({propKey: newKey, loading: true}, this.fetchPropBreakdown)
|
||||
}
|
||||
|
||||
renderBody() {
|
||||
if (this.state.loading) {
|
||||
return <div className="px-4 py-2"><div className="loading sm mx-auto"><div></div></div></div>
|
||||
} else {
|
||||
return this.state.breakdown.map((metaValue) => this.renderMetadataValue(metaValue))
|
||||
return this.state.breakdown.map((propValue) => this.renderPropValue(propValue))
|
||||
}
|
||||
}
|
||||
|
||||
renderPill(key) {
|
||||
const isActive = this.state.metaKey === key
|
||||
const isActive = this.state.propKey === key
|
||||
|
||||
if (isActive) {
|
||||
return <li key={key} className="inline-block h-5 text-indigo-700 font-bold border-b-2 border-indigo-700">{key}</li>
|
||||
} else {
|
||||
return <li key={key} className="hover:text-indigo-700 cursor-pointer" onClick={this.changeMetaKey.bind(this, key)}>{key}</li>
|
||||
return <li key={key} className="hover:text-indigo-700 cursor-pointer" onClick={this.changePropKey.bind(this, key)}>{key}</li>
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ export default class MetaBreakdown extends React.Component {
|
||||
<div className="flex items-center pb-1">
|
||||
<span className="text-xs font-bold text-gray-600">Breakdown by:</span>
|
||||
<ul className="flex font-medium text-xs text-gray-500 space-x-2 leading-5 pl-1">
|
||||
{ this.props.goal.meta_keys.map(this.renderPill.bind(this)) }
|
||||
{ this.props.goal.prop_names.map(this.renderPill.bind(this)) }
|
||||
</ul>
|
||||
</div>
|
||||
{ this.renderBody() }
|
@ -597,7 +597,7 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
ClickhouseRepo.exists?(from e in "events", where: e.domain == ^site.domain)
|
||||
end
|
||||
|
||||
def all_seen_metadata_keys(site, %Query{filters: %{"meta" => meta}} = query) when is_map(meta) do
|
||||
def all_props(site, %Query{filters: %{"props" => meta}} = query) when is_map(meta) do
|
||||
[{key, val}] = meta |> Enum.into([])
|
||||
|
||||
if val == "(none)" do
|
||||
@ -614,7 +614,7 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
end
|
||||
end
|
||||
|
||||
def all_seen_metadata_keys(site, query) do
|
||||
def all_props(site, query) do
|
||||
ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions_bare(site, query),
|
||||
inner_lateral_join: meta in fragment("meta as m"),
|
||||
@ -625,7 +625,7 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
end)
|
||||
end
|
||||
|
||||
def metadata_breakdown(site, %Query{filters: %{"meta" => meta}} = query, key) when is_map(meta) do
|
||||
def property_breakdown(site, %Query{filters: %{"props" => meta}} = query, key) when is_map(meta) do
|
||||
[{_key, val}] = meta |> Enum.into([])
|
||||
|
||||
if val == "(none)" do
|
||||
@ -653,7 +653,7 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
end
|
||||
end
|
||||
|
||||
def metadata_breakdown(site, query, key) do
|
||||
def property_breakdown(site, query, key) do
|
||||
none = ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
where: fragment("not has(meta.key, ?)", ^key),
|
||||
@ -858,8 +858,8 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
q
|
||||
end
|
||||
|
||||
if query.filters["meta"] do
|
||||
[{key, val}] = query.filters["meta"] |> Enum.into([])
|
||||
if query.filters["props"] do
|
||||
[{key, val}] = query.filters["props"] |> Enum.into([])
|
||||
|
||||
if val == "(none)" do
|
||||
from(
|
||||
@ -1070,8 +1070,8 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
q
|
||||
end
|
||||
|
||||
q = if query.filters["meta"] do
|
||||
[{key, val}] = query.filters["meta"] |> Enum.into([])
|
||||
q = if query.filters["props"] do
|
||||
[{key, val}] = query.filters["props"] |> Enum.into([])
|
||||
|
||||
if val == "(none)" do
|
||||
from(
|
||||
|
@ -117,7 +117,7 @@ defmodule PlausibleWeb.Api.ExternalController do
|
||||
end
|
||||
|
||||
defp parse_meta(params) do
|
||||
raw_meta = params["m"] || params["meta"]
|
||||
raw_meta = params["m"] || params["meta"] || params["p"] || params["props"]
|
||||
if raw_meta do
|
||||
Jason.decode!(raw_meta)
|
||||
else
|
||||
|
@ -36,7 +36,7 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
end
|
||||
|
||||
defp fetch_top_stats(site, %Query{filters: %{"goal" => goal}} = query) when is_binary(goal) do
|
||||
total_filter = Map.merge(query.filters, %{"goal" => nil, "meta" => nil})
|
||||
total_filter = Map.merge(query.filters, %{"goal" => nil, "props" => nil})
|
||||
prev_query = Query.shift_back(query)
|
||||
unique_visitors = Stats.unique_visitors(site, %{query | filters: total_filter})
|
||||
prev_unique_visitors = Stats.unique_visitors(site, %{prev_query | filters: total_filter})
|
||||
@ -260,18 +260,18 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
def conversions(conn, params) do
|
||||
site = conn.assigns[:site]
|
||||
query = Query.from(site.timezone, params)
|
||||
metadata_keys = Stats.all_seen_metadata_keys(site, query)
|
||||
prop_names = Stats.all_props(site, query)
|
||||
conversions = Stats.goal_conversions(site, query)
|
||||
|> Enum.map(fn goal -> Map.put(goal, :meta_keys, metadata_keys[goal[:name]]) end)
|
||||
|> Enum.map(fn goal -> Map.put(goal, :prop_names, prop_names[goal[:name]]) end)
|
||||
|
||||
json(conn, conversions)
|
||||
end
|
||||
|
||||
def meta_breakdown(conn, params) do
|
||||
def prop_breakdown(conn, params) do
|
||||
site = conn.assigns[:site]
|
||||
query = Query.from(site.timezone, params)
|
||||
|
||||
json(conn, Stats.metadata_breakdown(site, query, params["meta_key"]))
|
||||
json(conn, Stats.property_breakdown(site, query, params["prop_name"]))
|
||||
end
|
||||
|
||||
def current_visitors(conn, _) do
|
||||
|
@ -59,7 +59,7 @@ defmodule PlausibleWeb.Router do
|
||||
get "/:domain/operating-systems", StatsController, :operating_systems
|
||||
get "/:domain/screen-sizes", StatsController, :screen_sizes
|
||||
get "/:domain/conversions", StatsController, :conversions
|
||||
get "/:domain/meta-breakdown/:meta_key", StatsController, :meta_breakdown
|
||||
get "/:domain/property/:prop_name", StatsController, :prop_breakdown
|
||||
end
|
||||
|
||||
scope "/api", PlausibleWeb do
|
||||
|
@ -1 +1 @@
|
||||
!function(i,r){"use strict";var o=i.location,s=i.document,e=s.querySelector('[src*="'+r+'"]'),l=e&&e.getAttribute("data-domain"),t=!1;function n(e,t){if(/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(o.hostname)||"file:"===o.protocol)return console.warn("Ignoring event on localhost");var n={};n.n=e,n.u=o.href,n.d=l,n.r=s.referrer||null,n.w=i.innerWidth,console.log(t),t&&t.meta&&(n.m=JSON.stringify(t.meta));var a=new XMLHttpRequest;a.open("POST",r+"/api/event",!0),a.setRequestHeader("Content-Type","text/plain"),a.send(JSON.stringify(n)),a.onreadystatechange=function(){4==a.readyState&&t&&t.callback&&t.callback()}}function a(){n("pageview")}try{var c,p=i.history;p.pushState&&(c=p.pushState,p.pushState=function(){c.apply(this,arguments),a()},i.addEventListener("popstate",a));var u=i.plausible&&i.plausible.q||[];i.plausible=n;for(var d=0;d<u.length;d++)n.apply(this,u[d]);"prerender"===s.visibilityState?s.addEventListener("visibilitychange",function(){t||"visible"!==s.visibilityState||(t=!0,a())}):a()}catch(e){console.error(e),(new Image).src=r+"/api/error?message="+encodeURIComponent(e.message)}}(window,"<%= base_url %>");
|
||||
!function(i,r){"use strict";var s=i.location,o=i.document,e=o.querySelector('[src*="'+r+'"]'),l=e&&e.getAttribute("data-domain"),t=!1;function n(e,t){if(/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(s.hostname)||"file:"===s.protocol)return console.warn("Ignoring event on localhost");var n={};n.n=e,n.u=s.href,n.d=l,n.r=o.referrer||null,n.w=i.innerWidth,t&&t.meta&&(n.m=JSON.stringify(t.meta)),t&&t.props&&(n.p=JSON.stringify(t.props));var a=new XMLHttpRequest;a.open("POST",r+"/api/event",!0),a.setRequestHeader("Content-Type","text/plain"),a.send(JSON.stringify(n)),a.onreadystatechange=function(){4==a.readyState&&t&&t.callback&&t.callback()}}function a(){n("pageview")}try{var p,c=i.history;c.pushState&&(p=c.pushState,c.pushState=function(){p.apply(this,arguments),a()},i.addEventListener("popstate",a));var u=i.plausible&&i.plausible.q||[];i.plausible=n;for(var d=0;d<u.length;d++)n.apply(this,u[d]);"prerender"===o.visibilityState?o.addEventListener("visibilitychange",function(){t||"visible"!==o.visibilityState||(t=!0,a())}):a()}catch(e){console.error(e),(new Image).src=r+"/api/error?message="+encodeURIComponent(e.message)}}(window,"<%= base_url %>");
|
@ -1 +1 @@
|
||||
!function(i,r){"use strict";var s=i.location,o=i.document,e=o.querySelector('[src*="'+r+'"]'),l=e&&e.getAttribute("data-domain"),t=!1;function n(e,t){if(/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(s.hostname)||"file:"===s.protocol)return console.warn("Ignoring event on localhost");var n={};n.n=e,n.u=s.href,n.d=l,n.r=o.referrer||null,n.w=i.innerWidth,console.log(t),t&&t.meta&&(n.m=JSON.stringify(t.meta)),n.h=1;var a=new XMLHttpRequest;a.open("POST",r+"/api/event",!0),a.setRequestHeader("Content-Type","text/plain"),a.send(JSON.stringify(n)),a.onreadystatechange=function(){4==a.readyState&&t&&t.callback&&t.callback()}}function a(){n("pageview")}try{var c,p=i.history;p.pushState&&(c=p.pushState,p.pushState=function(){c.apply(this,arguments),a()},i.addEventListener("popstate",a)),i.addEventListener("hashchange",a);var u=i.plausible&&i.plausible.q||[];i.plausible=n;for(var h=0;h<u.length;h++)n.apply(this,u[h]);"prerender"===o.visibilityState?o.addEventListener("visibilitychange",function(){t||"visible"!==o.visibilityState||(t=!0,a())}):a()}catch(e){console.error(e),(new Image).src=r+"/api/error?message="+encodeURIComponent(e.message)}}(window,"<%= base_url %>");
|
||||
!function(i,r){"use strict";var s=i.location,o=i.document,e=o.querySelector('[src*="'+r+'"]'),l=e&&e.getAttribute("data-domain"),t=!1;function n(e,t){if(/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(s.hostname)||"file:"===s.protocol)return console.warn("Ignoring event on localhost");var n={};n.n=e,n.u=s.href,n.d=l,n.r=o.referrer||null,n.w=i.innerWidth,t&&t.meta&&(n.m=JSON.stringify(t.meta)),t&&t.props&&(n.p=JSON.stringify(t.props)),n.h=1;var a=new XMLHttpRequest;a.open("POST",r+"/api/event",!0),a.setRequestHeader("Content-Type","text/plain"),a.send(JSON.stringify(n)),a.onreadystatechange=function(){4==a.readyState&&t&&t.callback&&t.callback()}}function a(){n("pageview")}try{var c,p=i.history;p.pushState&&(c=p.pushState,p.pushState=function(){c.apply(this,arguments),a()},i.addEventListener("popstate",a)),i.addEventListener("hashchange",a);var u=i.plausible&&i.plausible.q||[];i.plausible=n;for(var h=0;h<u.length;h++)n.apply(this,u[h]);"prerender"===o.visibilityState?o.addEventListener("visibilitychange",function(){t||"visible"!==o.visibilityState||(t=!0,a())}):a()}catch(e){console.error(e),(new Image).src=r+"/api/error?message="+encodeURIComponent(e.message)}}(window,"<%= base_url %>");
|
@ -1 +1 @@
|
||||
!function(i,r){"use strict";var o=i.location,s=i.document,e=s.querySelector('[src*="'+r+'"]'),l=e&&e.getAttribute("data-domain"),t=!1;function n(e,t){if(/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(o.hostname)||"file:"===o.protocol)return console.warn("Ignoring event on localhost");var n={};n.n=e,n.u=o.href,n.d=l,n.r=s.referrer||null,n.w=i.innerWidth,console.log(t),t&&t.meta&&(n.m=JSON.stringify(t.meta));var a=new XMLHttpRequest;a.open("POST",r+"/api/event",!0),a.setRequestHeader("Content-Type","text/plain"),a.send(JSON.stringify(n)),a.onreadystatechange=function(){4==a.readyState&&t&&t.callback&&t.callback()}}function a(){n("pageview")}try{var c,p=i.history;p.pushState&&(c=p.pushState,p.pushState=function(){c.apply(this,arguments),a()},i.addEventListener("popstate",a));var u=i.plausible&&i.plausible.q||[];i.plausible=n;for(var d=0;d<u.length;d++)n.apply(this,u[d]);"prerender"===s.visibilityState?s.addEventListener("visibilitychange",function(){t||"visible"!==s.visibilityState||(t=!0,a())}):a()}catch(e){console.error(e),(new Image).src=r+"/api/error?message="+encodeURIComponent(e.message)}}(window,"<%= base_url %>");
|
||||
!function(i,r){"use strict";var s=i.location,o=i.document,e=o.querySelector('[src*="'+r+'"]'),l=e&&e.getAttribute("data-domain"),t=!1;function n(e,t){if(/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(s.hostname)||"file:"===s.protocol)return console.warn("Ignoring event on localhost");var n={};n.n=e,n.u=s.href,n.d=l,n.r=o.referrer||null,n.w=i.innerWidth,t&&t.meta&&(n.m=JSON.stringify(t.meta)),t&&t.props&&(n.p=JSON.stringify(t.props));var a=new XMLHttpRequest;a.open("POST",r+"/api/event",!0),a.setRequestHeader("Content-Type","text/plain"),a.send(JSON.stringify(n)),a.onreadystatechange=function(){4==a.readyState&&t&&t.callback&&t.callback()}}function a(){n("pageview")}try{var p,c=i.history;c.pushState&&(p=c.pushState,c.pushState=function(){p.apply(this,arguments),a()},i.addEventListener("popstate",a));var u=i.plausible&&i.plausible.q||[];i.plausible=n;for(var d=0;d<u.length;d++)n.apply(this,u[d]);"prerender"===o.visibilityState?o.addEventListener("visibilitychange",function(){t||"visible"!==o.visibilityState||(t=!0,a())}):a()}catch(e){console.error(e),(new Image).src=r+"/api/error?message="+encodeURIComponent(e.message)}}(window,"<%= base_url %>");
|
@ -20,6 +20,9 @@
|
||||
if (options && options.meta) {
|
||||
payload.m = JSON.stringify(options.meta)
|
||||
}
|
||||
if (options && options.props) {
|
||||
payload.p = JSON.stringify(options.props)
|
||||
}
|
||||
{{#if hashMode}}
|
||||
payload.h = 1
|
||||
{{/if}}
|
||||
|
Loading…
Reference in New Issue
Block a user