Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Continued] Google Analytics import #1753

Merged
merged 148 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
148 commits
Select commit Hold shift + click to select a range
aac8620
Add has_imported_stats boolean to Site
m-col Nov 10, 2021
9bae8c7
Add Google Analytics import panel to general settings
m-col Nov 10, 2021
167f31a
Get GA profiles to display in import settings panel
m-col Nov 10, 2021
1855942
Add import_from_google method as entrypoint to import data
m-col Nov 10, 2021
9abe8d3
Add imported_visitors table
m-col Nov 12, 2021
25061f2
Remove conflicting code from migration
m-col Nov 12, 2021
5a292ca
Import visitors data into clickhouse database
m-col Nov 12, 2021
56a5b5e
Pass another dataset to main graph for rendering in red
m-col Oct 21, 2021
e0ca9bb
Hook imported GA data to dashboard timeseries plot
m-col Nov 17, 2021
bffb113
Add settings option to forget imported data
m-col Nov 17, 2021
54703d2
Import sources from google analytics
m-col Nov 18, 2021
b5b5cec
Merge imported sources when queried
m-col Nov 19, 2021
e6ab270
Merge imported source data native data when querying sources
m-col Nov 23, 2021
0c5c4b7
Start converting metrics to atoms so they can be subqueried
m-col Nov 24, 2021
9e1b46d
Convery GA (direct) source to empty string
m-col Nov 24, 2021
a2c37fb
Import utm campaign and utm medium from GA
m-col Nov 24, 2021
be412a3
format
m-col Nov 24, 2021
afe3535
Import all data types from GA into new tables
m-col Nov 29, 2021
9ea2cd3
Handle large amounts of more data more safely
m-col Nov 29, 2021
f5b8f95
Fix some mistakes in tables
m-col Nov 29, 2021
dd0462e
Make GA requests in chunks of 5 queries
m-col Nov 29, 2021
2b86326
Only display imported timeseries when there is no filter
m-col Nov 29, 2021
8a969ea
Correctly show last 30 minutes timeseries when 'realtime'
m-col Nov 29, 2021
5b09c8c
Add with_imported key to Query struct
m-col Nov 29, 2021
e2c185a
Account for injected :is_not filter on sources from dashboard
m-col Nov 29, 2021
0a62a4d
Also add tentative imported_utm_sources table
m-col Nov 29, 2021
5a0a81d
Return imported data to dashboard for rest of Sources panel
m-col Nov 29, 2021
fa65a50
Clear imported stats from all tables when requested
m-col Nov 29, 2021
a8b0b10
Merge entry pages and exit pages from imported data into unfiltered d…
m-col Nov 29, 2021
b8ee7df
Display imported devices, browsers and OSs on dashboard
m-col Nov 29, 2021
0e57d58
Display imported country data on dashboard
m-col Nov 29, 2021
aac954d
Add more metrics to entries/exits for modals
m-col Nov 29, 2021
f2d7eab
make sure data is returned via API with correct keys
m-col Dec 1, 2021
3ab7d57
Import regions and cities from GA
m-col Dec 1, 2021
87ba204
Capitalize device upon import to match native data
m-col Dec 1, 2021
c5bc70a
Leave query limits/offsets until after possibly joining with imported…
m-col Dec 1, 2021
7936e6f
Also import timeOnPage and pageviews for pages from GA
m-col Dec 1, 2021
bca7e24
imported_countries -> imported_locations
m-col Dec 1, 2021
3f38a3e
Get timeOnPage and pageviews for pages from GA
m-col Dec 1, 2021
a03759e
Add indicator to dashboard when imported data is being used
m-col Dec 2, 2021
b8b8ccf
Don't show imported data as separately line on main graph
m-col Dec 2, 2021
991cf88
"bounce_rate" -> :bounce_rate, so it works in subqueries
m-col Dec 2, 2021
e03e128
Drop imported browser and OS versions
m-col Dec 3, 2021
5dbde9e
Toggle displaying imported data by clicking indicator
m-col Dec 6, 2021
71abc96
Parse referrers with RefInspector
m-col Dec 7, 2021
4bfee86
Keep imported data indicator on dashboard and strikethrough when hidden
m-col Dec 7, 2021
5ff0412
Add unlink google button to import panel
m-col Dec 8, 2021
2c6255e
Rename some GA browsers and OSes to plausible versions
m-col Dec 9, 2021
543f42b
Get main top pages and exit pages panels working correctly with impor…
m-col Dec 9, 2021
21fcada
mix format
m-col Dec 9, 2021
a7da346
Fetch time_on_pages for imported data when needed
m-col Dec 10, 2021
6ca72a2
entry pages need to fetch bounces from GA
m-col Dec 14, 2021
93ddbab
"sample_percent" -> :sample_percent as only atoms can be used in subq…
m-col Dec 14, 2021
67fd47a
Calculate bounce_rate for joined native and imported data for top pag…
m-col Dec 14, 2021
2158098
Flip some query bindings around to be less misleading
m-col Dec 14, 2021
8083261
Fixup entry page modal visit durations
m-col Dec 14, 2021
2881946
mix format
m-col Dec 14, 2021
d3d9a5c
Fetch bounces and visit_duration for sources from GA
m-col Dec 15, 2021
46fb187
add more source metrics used for data in modals
m-col Dec 16, 2021
e95e92b
Make sources modals display correct values
m-col Dec 16, 2021
e78c2e5
imported_visitors: bounce_rate -> bounces, avg_visit_duration -> visi…
m-col Dec 16, 2021
9e3963a
Merge imported data into aggregate stats
m-col Dec 18, 2021
429ec25
Reformat top graph side icons
m-col Dec 21, 2021
393ad27
Ensure sample_percent is yielded from aggregate data
m-col Dec 21, 2021
d446486
filter event_props should be strings
m-col Dec 21, 2021
adb3f22
Hide imported data from frontend when using filter
m-col Dec 21, 2021
f737aa7
Fix existing tests
m-col Dec 23, 2021
c6b314f
fix tests
m-col Dec 29, 2021
ae84c7e
Fix imported indicator appearing when filtering
m-col Dec 30, 2021
ecfccd4
comma needed, lost when rebasing
m-col Dec 31, 2021
c48b3b5
Import utm_terms and utm_content from GA
m-col Dec 31, 2021
bb6b085
Merge imported utm_term and utm_content
m-col Dec 31, 2021
866097e
Rename imported Countries data as Locations
m-col Dec 31, 2021
98f27f0
Set imported city schema field to int
m-col Jan 2, 2022
ca5f1e5
Remove utm_terms and utm_content when clearing imported
m-col Jan 2, 2022
944d07a
Clean locations import from Google Analytics
m-col Jan 2, 2022
c14ded5
Display imported region and city in dashboard
m-col Jan 2, 2022
787ee44
os -> operating_system in some parts of code
m-col Jan 3, 2022
e0a06f0
to_atom -> to_existing_atom
m-col Jan 3, 2022
decc32f
format
m-col Jan 3, 2022
7a1695f
"events" metric -> :events
m-col Jan 4, 2022
8f35671
ignore imported data when "events" in metrics
m-col Jan 4, 2022
1d85271
update "bounce_rate"
m-col Jan 4, 2022
0c50991
atomise some more metrics from new city and region api
m-col Jan 4, 2022
afdd562
atomise some more metrics for email handlers
m-col Jan 4, 2022
9aeca6f
"conversion_rate" -> :conversion_rate during csv export
m-col Jan 4, 2022
173df31
Move imported data stats code to own module
m-col Jan 5, 2022
f09a99d
Move imported timeseries function to Stats.Imported
m-col Jan 5, 2022
8acc085
Use Timex.parse to import dates from GA
m-col Jan 5, 2022
95e38a4
has_imported_stats -> imported_source
m-col Jan 5, 2022
88d925d
"time_on_page" -> :time_on_page
m-col Jan 5, 2022
b337e72
Convert imported GA data to UTC
m-col Jan 5, 2022
0e48b25
Clean up GA request code a bit
m-col Jan 5, 2022
11821fd
Fail sooner if GA timezone can't be identified
m-col Jan 5, 2022
ca8531b
Link imported tables to site by id
m-col Jan 6, 2022
b534f62
imported_utm_content -> imported_utm_contents
m-col Jan 7, 2022
a0e2167
Imported GA from all of time
m-col Jan 7, 2022
1505e7b
Reorganise GA data fetch logic
m-col Jan 7, 2022
de3220d
Clarify removal of "visits" data when it isn't in metrics
m-col Jan 7, 2022
5b57143
Apply location filters from API
m-col Jan 7, 2022
734a520
Do not use changeset for setting site.imported_source
m-col Jan 7, 2022
1a6ab5b
Add all metrics to all dimensions
m-col Jan 15, 2022
bbbc625
Run GA import in the background
m-col Jan 21, 2022
2ba78d3
Send email when GA import completes
m-col Jan 23, 2022
8eba626
Add handler to insert imported data into tests and imported_browsers_…
m-col Jan 23, 2022
680708f
Add remaining import data test factories
m-col Jan 27, 2022
4cb02e5
Add imported location data to test
m-col Jan 27, 2022
6309562
Test main graph with imported data
m-col Jan 27, 2022
516c84d
Add imported data to operating systems tests
m-col Jan 27, 2022
4132f64
Add imported data to pages tests
m-col Jan 27, 2022
51ca030
Add imported data to entry pages tests
m-col Jan 27, 2022
2e13a60
Add imported data to exit pages tests
m-col Jan 27, 2022
d369ba3
Add imported data to devices tests
m-col Jan 27, 2022
16a9d01
Add imported data to sources tests
m-col Jan 27, 2022
67fba86
Add imported data to UTM tests
m-col Jan 28, 2022
5ac5ba6
Add new test module for the data import step
m-col Jan 28, 2022
cb9ce03
Test import of sources GA data
m-col Jan 29, 2022
de821d6
Test import of utm_mediums GA data
m-col Jan 29, 2022
d646757
Test import of utm_campaigns GA data
m-col Jan 29, 2022
cd17fe5
Add tests for UTM terms
m-col Jan 29, 2022
72303d8
Add tests for UTM contents
m-col Jan 29, 2022
8b90896
Add test for importing pages and entry pages data from GA
m-col Jan 29, 2022
380f9aa
Add test for importing exit page data
m-col Jan 29, 2022
a29a7c1
Fix module file name typo
m-col Jan 29, 2022
99e3c15
Add test for importing location data from GA
m-col Jan 29, 2022
82e50cb
Add test for importing devices data from GA
m-col Jan 29, 2022
f5603f9
Add test for importing browsers data from GA
m-col Jan 29, 2022
b37e814
Add test for importing OS data from GA
m-col Jan 29, 2022
55da66a
Paginate GA requests to download all data
m-col Jan 29, 2022
2ff7606
Bump clickhouse_ecto version
m-col Feb 7, 2022
ba28622
Move RefInspector wrapper function into module
m-col Feb 7, 2022
df6d1bc
Drop timezone transform on import
m-col Feb 7, 2022
92ffae1
Order imported by side_id then date
m-col Feb 7, 2022
0e9c045
More strings -> atoms
m-col Feb 8, 2022
fdf4d00
Remove parallelisation of data import
m-col Feb 8, 2022
0cb66d6
Split sources and UTM sources from fetched GA data
m-col Feb 10, 2022
175025b
Keep prop names in queries as strings
m-col Feb 10, 2022
c574abe
fix typo
m-col Feb 15, 2022
9213b29
Fix import
ukutaht Mar 2, 2022
5073b39
Insert data to clickhouse in batches
ukutaht Mar 3, 2022
0f99441
Fix link when removing imported data
ukutaht Mar 3, 2022
16c8cb1
Merge source tables
ukutaht Mar 4, 2022
c324395
Import hostname as well as pathname
ukutaht Mar 4, 2022
d4b554b
Record start and end time of imported data
ukutaht Mar 7, 2022
8a0b532
Track import progress
ukutaht Mar 7, 2022
a7bc60f
Fix month interval with imported data
ukutaht Mar 8, 2022
8ddbfb4
Do not JOIN when imported date range has no overlap
ukutaht Mar 8, 2022
bd7e40b
Fix time on page using exits
ukutaht Mar 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/js/dashboard/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function serializeQuery(query, extraQuery=[]) {
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 }
Object.assign(queryObj, ...extraQuery)

Expand Down
1 change: 1 addition & 0 deletions assets/js/dashboard/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export function parseQuery(querystring, site) {
date: q.get('date') ? parseUTCDate(q.get('date')) : nowForSite(site),
from: q.get('from') ? parseUTCDate(q.get('from')) : undefined,
to: q.get('to') ? parseUTCDate(q.get('to')) : undefined,
with_imported: q.get('with_imported') ? q.get('with_imported') === 'true' : true,
filters: {
'goal': q.get('goal'),
'props': JSON.parse(q.get('props')),
Expand Down
49 changes: 38 additions & 11 deletions assets/js/dashboard/stats/visitor-graph.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react';
import { withRouter } from 'react-router-dom'
import { withRouter, Link } from 'react-router-dom'
import Chart from 'chart.js/auto';
import { navigateToQuery } from '../query'
import numberFormatter, {durationFormatter} from '../util/number-formatter'
import * as api from '../api'
import LazyLoader from '../components/lazy-loader'
import * as url from '../util/url'

function buildDataSet(plot, present_index, ctx, label) {
var gradient = ctx.createLinearGradient(0, 0, 0, 300);
Expand Down Expand Up @@ -316,17 +317,19 @@ class LineGraph extends React.Component {

if (this.state.exported) {
return (
<svg className="animate-spin h-4 w-4 text-indigo-500 absolute -top-8 right-8" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<div className="flex-auto w-4 h-4">
<svg className="animate-spin h-4 w-4 text-indigo-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
)
} else {
const endpoint = `/${encodeURIComponent(this.props.site.domain)}/export${api.serializeQuery(this.props.query)}`

return (
<a href={endpoint} download onClick={this.downloadSpinner.bind(this)}>
<svg className="absolute w-4 h-5 text-gray-700 feather dark:text-gray-300 -top-8 right-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
<a className="flex-auto w-4 h-4" href={endpoint} download onClick={this.downloadSpinner.bind(this)}>
<svg className="absolute text-gray-700 feather dark:text-gray-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
</a>
)
}
Expand All @@ -338,15 +341,36 @@ class LineGraph extends React.Component {

if (samplePercent < 100) {
return (
<div tooltip={`Stats based on a ${samplePercent}% sample of all visitors`} className="absolute cursor-pointer -top-8 right-14 lg:-top-20 lg:right-8">
<svg className="w-4 h-4 text-gray-300 text-gray-700" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div tooltip={`Stats based on a ${samplePercent}% sample of all visitors`} className="cursor-pointer flex-auto w-4 h-4">
<svg className="absolute w-4 h-4 text-gray-300 text-gray-700" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
)
}
}

importedNotice() {
const source = this.props.graphData.imported_source;

if (source) {
const withImported = this.props.graphData.with_imported;
const strike = withImported ? "" : " line-through"
const target = url.setQuery('with_imported', !withImported)
const tip = withImported ? "" : "do not ";

return (
<Link to={target} className="w-4 h-4">
<div tooltip={`Stats ${tip}include data imported from ${source}.`} className="cursor-pointer flex-auto w-4 h-4">
<svg className="absolute text-gray-300 text-gray-700" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<text x="4" y="18" fontSize="24" fill="currentColor" className={"text-gray-700 dark:text-gray-300" + strike}>{ source[0].toUpperCase() }</text>
</svg>
</div>
</Link>
)
}
}

render() {
const extraClass = this.props.graphData.interval === 'hour' ? '' : 'cursor-pointer'

Expand All @@ -356,8 +380,11 @@ class LineGraph extends React.Component {
{ this.renderTopStats() }
</div>
<div className="relative px-2">
{ this.downloadLink() }
{ this.samplingNotice() }
<div className="absolute right-2 -top-5 lg:-top-20 lg:w-6 w-16 lg:h-20 flex lg:flex-col flex-row">
{ this.downloadLink() }
{ this.samplingNotice() }
{ this.importedNotice() }
</div>
<canvas id="main-graph-canvas" className={'mt-4 ' + extraClass} width="1054" height="342"></canvas>
</div>
</div>
Expand Down
3 changes: 3 additions & 0 deletions config/.env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ SHOW_CITIES=true
PADDLE_VENDOR_AUTH_CODE=895e20d4efaec0575bb857f44b183217b332d9592e76e69b8a
PADDLE_VENDOR_ID=3942

GOOGLE_CLIENT_ID=875387135161-l8tp53dpt7fdhdg9m1pc3vl42si95rh0.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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

IP_GEOLOCATION_DB=/home/ukutaht/plausible/analytics/city_database.mmdb
5 changes: 3 additions & 2 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,8 @@ if config_env() == :prod && !disable_cron do
check_stats_emails: 1,
site_setup_emails: 1,
clean_email_verification_codes: 1,
clean_invitations: 1
clean_invitations: 1,
google_analytics_imports: 1
]

extra_queues = [
Expand All @@ -340,7 +341,7 @@ if config_env() == :prod && !disable_cron do
else
config :plausible, Oban,
repo: Plausible.Repo,
queues: false,
queues: [google_analytics_imports: 1],
plugins: false
end

Expand Down
18 changes: 18 additions & 0 deletions lib/plausible/clickhouse_repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,22 @@ defmodule Plausible.ClickhouseRepo do
Ecto.Adapters.SQL.query!(__MODULE__, events_sql, [domain])
Ecto.Adapters.SQL.query!(__MODULE__, sessions_sql, [domain])
end

def clear_imported_stats_for(site_id) do
[
"imported_visitors",
"imported_sources",
"imported_pages",
"imported_entry_pages",
"imported_exit_pages",
"imported_locations",
"imported_devices",
"imported_browsers",
"imported_operating_systems"
]
|> Enum.map(fn table ->
sql = "ALTER TABLE #{table} DELETE WHERE site_id = ?"
Ecto.Adapters.SQL.query!(__MODULE__, sql, [site_id])
end)
end
end
Loading