diff --git a/assets/js/dashboard/api.js b/assets/js/dashboard/api.js index a9ff924e44..bb1749ddfa 100644 --- a/assets/js/dashboard/api.js +++ b/assets/js/dashboard/api.js @@ -59,8 +59,8 @@ export function get(url, query = {}, ...extraQuery) { return fetch(url, { signal: abortController.signal, headers: headers }) .then(response => { if (!response.ok) { - return response.json().then((msg) => { - throw new ApiError(msg.error, msg) + return response.json().then((payload) => { + throw new ApiError(payload.error, payload) }) } return response.json() diff --git a/assets/js/dashboard/hooks/api-client.js b/assets/js/dashboard/hooks/api-client.js index 9e3c0fd085..903244e3a9 100644 --- a/assets/js/dashboard/hooks/api-client.js +++ b/assets/js/dashboard/hooks/api-client.js @@ -71,7 +71,8 @@ export function useAPIClient(props) { const getNextPageParam = (lastPageResults, _, lastPageIndex) => { return lastPageResults.length === LIMIT ? lastPageIndex + 1 : null } - const initialPageParam = 1 + const defaultInitialPageParam = 1 + const initialPageParam = props.initialPageParam === undefined ? defaultInitialPageParam : props.initialPageParam return useInfiniteQuery({ queryKey: key, diff --git a/assets/js/dashboard/stats/modals/breakdown-modal.js b/assets/js/dashboard/stats/modals/breakdown-modal.js index a938e0e145..d97b50c3e0 100644 --- a/assets/js/dashboard/stats/modals/breakdown-modal.js +++ b/assets/js/dashboard/stats/modals/breakdown-modal.js @@ -6,7 +6,8 @@ import { useQueryContext } from "../../query-context"; import { useSiteContext } from "../../site-context"; import { useDebounce } from "../../custom-hooks"; import { useAPIClient } from "../../hooks/api-client"; -const MIN_HEIGHT_PX = 500 + +export const MIN_HEIGHT_PX = 500 // The main function component for rendering the "Details" reports on the dashboard, // i.e. a breakdown by a single (non-time) dimension, with a given set of metrics. diff --git a/assets/js/dashboard/stats/modals/google-keywords.js b/assets/js/dashboard/stats/modals/google-keywords.js index 4dfd459726..498489c144 100644 --- a/assets/js/dashboard/stats/modals/google-keywords.js +++ b/assets/js/dashboard/stats/modals/google-keywords.js @@ -1,118 +1,193 @@ -import React from "react"; -import { Link, withRouter } from 'react-router-dom' +import React, { useState, useEffect, useRef } from "react"; import Modal from './modal' -import * as api from '../../api' -import numberFormatter, { percentageFormatter } from '../../util/number-formatter' -import { parseQuery } from '../../query' import RocketIcon from './rocket-icon' +import { useQueryContext } from "../../query-context"; +import { useSiteContext } from "../../site-context"; +import { useAPIClient } from "../../hooks/api-client"; +import { useDebounce } from "../../custom-hooks"; +import { createVisitors, Metric, renderNumberWithTooltip } from "../reports/metrics"; +import numberFormatter, { percentageFormatter } from "../../util/number-formatter"; +import classNames from "classnames"; +import { MIN_HEIGHT_PX } from "./breakdown-modal"; -class GoogleKeywordsModal extends React.Component { - constructor(props) { - super(props) - this.state = { - loading: true, - query: parseQuery(props.location.search, props.site) - } - } +function GoogleKeywordsModal() { + const searchBoxRef = useRef(null) + const { query } = useQueryContext() + const site = useSiteContext() + const endpoint = `/api/stats/${encodeURIComponent(site.domain)}/referrers/Google` - componentDidMount() { - 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, - notConfigured: res.not_configured, - isOwner: res.is_owner - })) - } + const [search, setSearch] = useState('') - renderTerm(term) { - return ( - - - {term.name} - {numberFormatter(term.visitors)} - {numberFormatter(term.impressions)} - {percentageFormatter(term.ctr)} - {numberFormatter(term.position)} - - - ) - } + const metrics = [ + createVisitors({renderLabel: (_query) => 'Visitors'}), + new Metric({key: 'impressions', renderLabel: (_query) => 'Impressions', renderValue: renderNumberWithTooltip}), + new Metric({key: 'ctr', renderLabel: (_query) => 'CTR', renderValue: percentageFormatter}), + new Metric({key: 'position', renderLabel: (_query) => 'Position', renderValue: numberFormatter}) + ] - renderKeywords() { - if (this.state.notConfigured) { - if (this.state.isOwner) { - return ( -
- -
The site is not connected to Google Search Keywords
-
Configure the integration to view search terms
- Connect with Google -
- ) - } else { - return ( -
- -
The site is not connected to Google Search Kewyords
-
Cannot show search terms
-
- ) + const { + data, + hasNextPage, + fetchNextPage, + isFetchingNextPage, + isFetching, + isPending, + error, + status + } = useAPIClient({ + key: [endpoint, {query, search}], + getRequestParams: (key) => { + const [_endpoint, {query, search}] = key + const params = { detailed: true } + + return [query, search === '' ? params : {...params, search}] + }, + initialPageParam: 0 + }) + + useEffect(() => { + const searchBox = searchBoxRef.current + + const handleKeyUp = (event) => { + if (event.key === 'Escape') { + event.target.blur() + event.stopPropagation() } - } else if (this.state.searchTerms.length > 0) { - return ( - - - - - - - - - - - - {this.state.searchTerms.map(this.renderTerm.bind(this))} - -
Search TermVisitorsImpressionsCTRPosition
- ) - } else { - return ( -
- -
Could not find any search terms for this period
-
- ) } - } - renderBody() { - if (this.state.loading) { - return ( -
- ) - } else { - return ( - - ← All referrers + searchBox.addEventListener('keyup', handleKeyUp); -
-
- {this.renderKeywords()} -
-
- ) + return () => { + searchBox.removeEventListener('keyup', handleKeyUp); } - } + }, []) - render() { + function renderRow(item) { return ( - - {this.renderBody()} - + + {item.name} + {metrics.map((metric) => { + return ( + + {metric.renderValue(item[metric.key])} + + ) + })} + ) } + + function renderInitialLoadingSpinner() { + return ( +
+
+
+ ) + } + + function renderSmallLoadingSpinner() { + return ( +
+ ) + } + + function renderLoadMoreButton() { + if (isPending) return null + if (!isFetching && !hasNextPage) return null + + return ( +
+ {!isFetching && } + {isFetchingNextPage && renderSmallLoadingSpinner()} +
+ ) + } + + function handleInputChange(e) { + setSearch(e.target.value) + } + + const debouncedHandleInputChange = useDebounce(handleInputChange) + + function renderSearchInput() { + const searchBoxClass = classNames('shadow-sm dark:bg-gray-900 dark:text-gray-100 focus:ring-indigo-500 focus:border-indigo-500 block sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:bg-gray-800 w-48', { + 'pointer-events-none' : status === 'error' + }) + return ( + + ) + } + + function renderModalBody() { + if (data?.pages?.length) { + return ( +
+ + + + + {metrics.map((metric) => { + return ( + + ) + })} + + + + {data.pages.map((p) => p.map(renderRow))} + +
+ Search term + + {metric.renderLabel(query)} +
+
+ ) + } + } + + function renderError() { + return ( +
+
+
{error.message}
+
+ ) + } + + return ( + +
+
+
+

Google Search Terms

+ {!isPending && isFetching && renderSmallLoadingSpinner()} +
+ {renderSearchInput()} +
+
+
+ {status === 'error' && renderError()} + {isPending && renderInitialLoadingSpinner()} + {!isPending && renderModalBody()} + {renderLoadMoreButton()} +
+
+
+ ) } -export default withRouter(GoogleKeywordsModal) +export default GoogleKeywordsModal diff --git a/assets/js/dashboard/stats/reports/metrics.js b/assets/js/dashboard/stats/reports/metrics.js index db5704e66f..be5a75e91e 100644 --- a/assets/js/dashboard/stats/reports/metrics.js +++ b/assets/js/dashboard/stats/reports/metrics.js @@ -159,6 +159,6 @@ export const createExitRate = (props) => { return new Metric({...props, key: "exit_rate", renderValue, renderLabel}) } -function renderNumberWithTooltip(value) { +export function renderNumberWithTooltip(value) { return {numberFormatter(value)} } \ No newline at end of file diff --git a/assets/js/dashboard/stats/sources/search-terms.js b/assets/js/dashboard/stats/sources/search-terms.js index 707c5177f2..2b8660d9fe 100644 --- a/assets/js/dashboard/stats/sources/search-terms.js +++ b/assets/js/dashboard/stats/sources/search-terms.js @@ -7,10 +7,19 @@ import RocketIcon from '../modals/rocket-icon' import * as api from '../../api' import LazyLoader from '../../components/lazy-loader' +export function ConfigureSearchTermsCTA({site}) { + return ( + <> +
Configure the integration to view search terms
+ Connect with Google + + ) +} + export default class SearchTerms extends React.Component { constructor(props) { super(props) - this.state = { loading: true } + this.state = { loading: true, errorPayload: null } this.onVisible = this.onVisible.bind(this) this.fetchSearchTerms = this.fetchSearchTerms.bind(this) } @@ -37,14 +46,11 @@ export default class SearchTerms extends React.Component { 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, - isAdmin: res.is_admin, - unsupportedFilters: res.unsupported_filters + searchTerms: res.results, + errorPayload: null })).catch((error) => { - this.setState({ loading: false, searchTerms: [], notConfigured: true, error: true, isAdmin: error.payload.is_admin }) - } - ) + this.setState({ loading: false, searchTerms: [], errorPayload: error.payload }) + }) } renderSearchTerm(term) { @@ -68,22 +74,14 @@ export default class SearchTerms extends React.Component { } renderList() { - if (this.state.unsupportedFilters) { + if (this.state.errorPayload) { + const {reason, is_admin, error} = this.state.errorPayload + return (
-
Unable to fetch keyword data from Search Console because it does not support the current set of filters
-
- ) - } else if (this.state.notConfigured) { - return ( -
- -
- This site is not connected to Search Console so we cannot show the search terms - {this.state.isAdmin && this.state.error && <>

Please click below to connect your Search Console account.

} -
- {this.state.isAdmin && Connect with Google} +
{error}
+ {reason === 'not_configured' && is_admin && }
) } else if (this.state.searchTerms.length > 0) { diff --git a/fixture/http_mocks/google_analytics_stats#with_page.json b/fixture/http_mocks/google_analytics_stats#with_page.json deleted file mode 100644 index f2fb9a6d95..0000000000 --- a/fixture/http_mocks/google_analytics_stats#with_page.json +++ /dev/null @@ -1,50 +0,0 @@ -[ - { - "status": 200, - "url": "https://www.googleapis.com/webmasters/v3/sites/sc-domain%3Adummy.test/searchAnalytics/query", - "method": "post", - "request_body": { - "dimensionFilterGroups": [ - { - "filters": [ - { - "dimension": "page", - "expression": "https://sc-domain%3Adummy.test5" - } - ] - } - ], - "dimensions": [ - "query" - ], - "endDate": "2022-01-05", - "rowLimit": 5, - "startDate": "2022-01-01" - }, - "response_body": { - "responseAggregationType": "auto", - "rows": [ - { - "clicks": 25.0, - "ctr": 0.3, - "impressions": 50.0, - "keys": [ - "keyword1", - "keyword2" - ], - "position": 2.0 - }, - { - "clicks": 15.0, - "ctr": 0.5, - "impressions": 25.0, - "keys": [ - "keyword3", - "keyword4" - ], - "position": 4.0 - } - ] - } - } -] \ No newline at end of file diff --git a/fixture/http_mocks/google_analytics_auth#invalid_grant.json b/fixture/http_mocks/google_auth#invalid_grant.json similarity index 100% rename from fixture/http_mocks/google_analytics_auth#invalid_grant.json rename to fixture/http_mocks/google_auth#invalid_grant.json diff --git a/fixture/http_mocks/google_analytics_stats.json b/fixture/http_mocks/google_search_console.json similarity index 97% rename from fixture/http_mocks/google_analytics_stats.json rename to fixture/http_mocks/google_search_console.json index 431b8fa2a7..994873088f 100644 --- a/fixture/http_mocks/google_analytics_stats.json +++ b/fixture/http_mocks/google_search_console.json @@ -10,6 +10,7 @@ ], "endDate": "2022-01-05", "rowLimit": 5, + "startRow": 0, "startDate": "2022-01-01" }, "response_body": { diff --git a/lib/plausible/google/api.ex b/lib/plausible/google/api.ex index c36dc1df92..7f186e4779 100644 --- a/lib/plausible/google/api.ex +++ b/lib/plausible/google/api.ex @@ -59,18 +59,18 @@ defmodule Plausible.Google.API do end end - def fetch_stats(site, query, limit) do + def fetch_stats(site, query, pagination, search) do with {:ok, site} <- ensure_search_console_property(site), {:ok, access_token} <- maybe_refresh_token(site.google_auth), - {:ok, search_console_filters} <- - SearchConsole.Filters.transform(site.google_auth.property, query.filters), + {:ok, gsc_filters} <- + SearchConsole.Filters.transform(site.google_auth.property, query.filters, search), {:ok, stats} <- HTTP.list_stats( access_token, site.google_auth.property, query.date_range, - limit, - search_console_filters + pagination, + gsc_filters ) do stats |> Map.get("rows", []) diff --git a/lib/plausible/google/http.ex b/lib/plausible/google/http.ex index c24808764b..4666cd0a44 100644 --- a/lib/plausible/google/http.ex +++ b/lib/plausible/google/http.ex @@ -39,12 +39,15 @@ defmodule Plausible.Google.HTTP do response.body end - def list_stats(access_token, property, date_range, limit, search_console_filters) do + def list_stats(access_token, property, date_range, pagination, search_console_filters) do + {limit, page} = pagination + params = %{ startDate: Date.to_iso8601(date_range.first), endDate: Date.to_iso8601(date_range.last), dimensions: ["query"], rowLimit: limit, + startRow: page * limit, dimensionFilterGroups: search_console_filters } @@ -65,7 +68,7 @@ defmodule Plausible.Google.HTTP do {:error, error} {:error, reason} -> - Logger.error("Google Analytics: failed to list stats: #{inspect(reason)}") + Logger.error("Google Search Console: failed to list stats: #{inspect(reason)}") {:error, "failed_to_list_stats"} end end diff --git a/lib/plausible/google/search_console/filters.ex b/lib/plausible/google/search_console/filters.ex index 43e10e1c87..beca3923dc 100644 --- a/lib/plausible/google/search_console/filters.ex +++ b/lib/plausible/google/search_console/filters.ex @@ -2,17 +2,18 @@ defmodule Plausible.Google.SearchConsole.Filters do @moduledoc false import Plausible.Stats.Filters.Utils, only: [page_regex: 1] - def transform(property, plausible_filters) do - search_console_filters = - Enum.reduce_while(plausible_filters, [], fn plausible_filter, search_console_filters -> + def transform(property, plausible_filters, search) do + gsc_filters = + Enum.reduce_while(plausible_filters, [], fn plausible_filter, gsc_filters -> case transform_filter(property, plausible_filter) do :unsupported -> {:halt, :unsupported_filters} - :ignore -> {:cont, search_console_filters} - search_console_filter -> {:cont, [search_console_filter | search_console_filters]} + :ignore -> {:cont, gsc_filters} + gsc_filter -> {:cont, [gsc_filter | gsc_filters]} end end) + |> maybe_add_search_filter(search) - case search_console_filters do + case gsc_filters do :unsupported_filters -> :unsupported_filters [] -> {:ok, []} filters when is_list(filters) -> {:ok, [%{filters: filters}]} @@ -64,4 +65,10 @@ defmodule Plausible.Google.SearchConsole.Filters do country = Location.Country.get_country(alpha_2) country.alpha_3 end + + defp maybe_add_search_filter(gsc_filters, search) when byte_size(search) > 0 do + [%{operator: "includingRegex", expression: search, dimension: "query"} | gsc_filters] + end + + defp maybe_add_search_filter(gsc_filters, _search), do: gsc_filters end diff --git a/lib/plausible_web/controllers/api/stats_controller.ex b/lib/plausible_web/controllers/api/stats_controller.ex index bfc34cbd87..6a65a7903f 100644 --- a/lib/plausible_web/controllers/api/stats_controller.ex +++ b/lib/plausible_web/controllers/api/stats_controller.ex @@ -752,23 +752,46 @@ defmodule PlausibleWeb.Api.StatsController do user_id = get_session(conn, :current_user_id) is_admin = user_id && Plausible.Sites.has_admin_access?(user_id, site) - case google_api().fetch_stats(site, query, params["limit"] || 9) do - {:error, :google_propery_not_configured} -> - json(conn, %{not_configured: true, is_admin: is_admin}) + pagination = { + to_int(params["limit"], 9), + to_int(params["page"], 0) + } + + search = params["search"] || "" + + not_configured_error_payload = + %{ + error: "The site is not connected to Google Search Keywords", + reason: :not_configured, + is_admin: is_admin + } + + unsupported_filters_error_payload = %{ + error: + "Unable to fetch keyword data from Search Console because it does not support the current set of filters", + reason: :unsupported_filters + } + + case google_api().fetch_stats(site, query, pagination, search) do + {:error, :google_property_not_configured} -> + conn + |> put_status(422) + |> json(not_configured_error_payload) {:error, :unsupported_filters} -> - json(conn, %{unsupported_filters: true}) + conn + |> put_status(422) + |> json(unsupported_filters_error_payload) {:ok, terms} -> - json(conn, %{search_terms: terms}) + json(conn, %{results: terms}) + + {:error, error} -> + Logger.error("Plausible.Google.API.fetch_stats failed with error: `#{inspect(error)}`") - {:error, _} -> conn |> put_status(502) - |> json(%{ - not_configured: true, - is_admin: is_admin - }) + |> json(not_configured_error_payload) end end diff --git a/test/plausible/google/api_test.exs b/test/plausible/google/api_test.exs index ce22268a98..cd020ca5ec 100644 --- a/test/plausible/google/api_test.exs +++ b/test/plausible/google/api_test.exs @@ -34,6 +34,7 @@ defmodule Plausible.Google.APITest do dimensions: ["query"], endDate: "2022-01-05", rowLimit: 5, + startRow: 0, startDate: "2022-01-01" } -> {:error, %{reason: %Finch.Response{status: Enum.random([401, 403])}}} @@ -42,7 +43,7 @@ defmodule Plausible.Google.APITest do query = %Plausible.Stats.Query{date_range: Date.range(~D[2022-01-01], ~D[2022-01-05])} - assert {:error, "google_auth_error"} = Google.API.fetch_stats(site, query, 5) + assert {:error, "google_auth_error"} = Google.API.fetch_stats(site, query, {5, 0}, "") end test "returns whatever error code google returns on API client error", %{site: site} do @@ -59,7 +60,7 @@ defmodule Plausible.Google.APITest do query = %Plausible.Stats.Query{date_range: Date.range(~D[2022-01-01], ~D[2022-01-05])} - assert {:error, "some_error"} = Google.API.fetch_stats(site, query, 5) + assert {:error, "some_error"} = Google.API.fetch_stats(site, query, {5, 0}, "") end test "returns generic HTTP error and logs it", %{site: site} do @@ -79,15 +80,16 @@ defmodule Plausible.Google.APITest do log = capture_log(fn -> assert {:error, "failed_to_list_stats"} = - Google.API.fetch_stats(site, query, 5) + Google.API.fetch_stats(site, query, {5, 0}, "") end) - assert log =~ "Google Analytics: failed to list stats: %Finch.Error{reason: :some_reason}" + assert log =~ + "Google Search Console: failed to list stats: %Finch.Error{reason: :some_reason}" end end test "returns error when token refresh fails", %{user: user, site: site} do - mock_http_with("google_analytics_auth#invalid_grant.json") + mock_http_with("google_auth#invalid_grant.json") insert(:google_auth, user: user, @@ -100,13 +102,13 @@ defmodule Plausible.Google.APITest do query = %Plausible.Stats.Query{date_range: Date.range(~D[2022-01-01], ~D[2022-01-05])} - assert {:error, "invalid_grant"} = Google.API.fetch_stats(site, query, 5) + assert {:error, "invalid_grant"} = Google.API.fetch_stats(site, query, 5, "") end test "returns error when google auth not configured", %{site: site} do query = %Plausible.Stats.Query{date_range: Date.range(~D[2022-01-01], ~D[2022-01-05])} - assert {:error, :google_property_not_configured} = Google.API.fetch_stats(site, query, 5) + assert {:error, :google_property_not_configured} = Google.API.fetch_stats(site, query, 5, "") end describe "fetch_stats/3 with valid auth" do @@ -122,7 +124,7 @@ defmodule Plausible.Google.APITest do end test "returns name and visitor count", %{site: site} do - mock_http_with("google_analytics_stats.json") + mock_http_with("google_search_console.json") query = %Plausible.Stats.Query{date_range: Date.range(~D[2022-01-01], ~D[2022-01-05])} @@ -130,7 +132,7 @@ defmodule Plausible.Google.APITest do [ %{name: "keyword1", visitors: 25, ctr: 36.8, impressions: 50, position: 2.2}, %{name: "keyword3", visitors: 15} - ]} = Google.API.fetch_stats(site, query, 5) + ]} = Google.API.fetch_stats(site, query, {5, 0}, "") end test "transforms page filters to search console format", %{site: site} do @@ -147,6 +149,7 @@ defmodule Plausible.Google.APITest do dimensions: ["query"], endDate: "2022-01-05", rowLimit: 5, + startRow: 0, startDate: "2022-01-01" } -> {:ok, %Finch.Response{status: 200, body: %{"rows" => []}}} @@ -161,7 +164,7 @@ defmodule Plausible.Google.APITest do "filters" => "event:page==/page" }) - assert {:ok, []} = Google.API.fetch_stats(site, query, 5) + assert {:ok, []} = Google.API.fetch_stats(site, query, {5, 0}, "") end test "returns :invalid filters when using filters that cannot be used in Search Console", %{ @@ -175,7 +178,7 @@ defmodule Plausible.Google.APITest do "filters" => "event:goal==Signup" }) - assert {:error, :unsupported_filters} = Google.API.fetch_stats(site, query, 5) + assert {:error, :unsupported_filters} = Google.API.fetch_stats(site, query, 5, "") end end end diff --git a/test/plausible/google/search_console/filters_test.exs b/test/plausible/google/search_console/filters_test.exs index 265df74762..83b0102cc6 100644 --- a/test/plausible/google/search_console/filters_test.exs +++ b/test/plausible/google/search_console/filters_test.exs @@ -7,7 +7,7 @@ defmodule Plausible.Google.SearchConsole.FiltersTest do [:is, "visit:entry_page", ["/page"]] ] - {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters) + {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters, "") assert transformed == [ %{ @@ -27,7 +27,7 @@ defmodule Plausible.Google.SearchConsole.FiltersTest do [:matches, "visit:entry_page", ["*page*"]] ] - {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters) + {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters, "") assert transformed == [ %{ @@ -47,7 +47,7 @@ defmodule Plausible.Google.SearchConsole.FiltersTest do [:is, "visit:entry_page", ["/pageA", "/pageB"]] ] - {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters) + {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters, "") assert transformed == [ %{ @@ -67,7 +67,7 @@ defmodule Plausible.Google.SearchConsole.FiltersTest do [:matches, "visit:entry_page", ["/pageA*", "/pageB*"]] ] - {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters) + {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters, "") assert transformed == [ %{ @@ -87,7 +87,7 @@ defmodule Plausible.Google.SearchConsole.FiltersTest do [:matches, "event:page", ["/pageA*", "/pageB*"]] ] - {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters) + {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters, "") assert transformed == [ %{ @@ -107,7 +107,7 @@ defmodule Plausible.Google.SearchConsole.FiltersTest do [:is, "visit:screen", ["Desktop"]] ] - {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters) + {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters, "") assert transformed == [ %{ @@ -123,7 +123,7 @@ defmodule Plausible.Google.SearchConsole.FiltersTest do [:is, "visit:screen", ["Mobile", "Tablet"]] ] - {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters) + {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters, "") assert transformed == [ %{ @@ -139,7 +139,7 @@ defmodule Plausible.Google.SearchConsole.FiltersTest do [:is, "visit:country", ["EE"]] ] - {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters) + {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters, "") assert transformed == [ %{filters: [%{dimension: "country", operator: "includingRegex", expression: "EST"}]} @@ -151,7 +151,7 @@ defmodule Plausible.Google.SearchConsole.FiltersTest do [:is, "visit:country", ["EE", "PL"]] ] - {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters) + {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters, "") assert transformed == [ %{ @@ -169,7 +169,7 @@ defmodule Plausible.Google.SearchConsole.FiltersTest do [:is, "visit:screen", ["Desktop"]] ] - {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters) + {:ok, transformed} = Filters.transform("sc-domain:plausible.io", filters, "") assert transformed == [ %{ @@ -194,6 +194,6 @@ defmodule Plausible.Google.SearchConsole.FiltersTest do [:is, "visit:utm_medium", "facebook"] ] - assert :unsupported_filters = Filters.transform("sc-domain:plausible.io", filters) + assert :unsupported_filters = Filters.transform("sc-domain:plausible.io", filters, "") end end diff --git a/test/plausible_web/controllers/api/stats_controller/sources_test.exs b/test/plausible_web/controllers/api/stats_controller/sources_test.exs index ea7b9c15a2..ba3184959e 100644 --- a/test/plausible_web/controllers/api/stats_controller/sources_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/sources_test.exs @@ -1626,9 +1626,9 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do ]) conn = get(conn, "/api/stats/#{site.domain}/referrers/Google?period=day") - {:ok, terms} = Plausible.Google.API.Mock.fetch_stats(nil, nil, nil) + {:ok, terms} = Plausible.Google.API.Mock.fetch_stats(nil, nil, nil, nil) - assert json_response(conn, 200) == %{"search_terms" => terms} + assert json_response(conn, 200) == %{"results" => terms} end test "works when filter expression is provided for source", %{ diff --git a/test/support/google_api_mock.ex b/test/support/google_api_mock.ex index 878cbec970..84329a6547 100644 --- a/test/support/google_api_mock.ex +++ b/test/support/google_api_mock.ex @@ -3,7 +3,7 @@ defmodule Plausible.Google.API.Mock do Mock of API to Google services. """ - def fetch_stats(_auth, _query, _limit) do + def fetch_stats(_auth, _query, _pagination, _search) do {:ok, [ %{"name" => "simple web analytics", "count" => 6},