Skip to content

Commit dcec868

Browse files
Fix filter handling for Google Search Console action (#16467)
* Fixing retrieve-site-perf-data action * Update pnpm-lock.yaml
1 parent 6d78852 commit dcec868

File tree

6 files changed

+207
-32
lines changed

6 files changed

+207
-32
lines changed
Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,39 @@
11
# Overview
22

3-
The Google Search Console API opens a treasure trove of data and insights about your websites presence in Google Search results. You can get detailed reports on your site's search traffic, manage and test your site's sitemaps and robots.txt files, and see which queries bring users to your site. On Pipedream, utilize this API to automate checks on site performance, integrate with other tools for deeper analysis, or keep tabs on your SEO strategy's effectiveness.
3+
The Google Search Console API opens a treasure trove of data and insights about your website's presence in Google Search results. You can get detailed reports on your site's search traffic, manage and test your site's sitemaps and robots.txt files, and see which queries bring users to your site. On Pipedream, utilize this API to automate checks on site performance, integrate with other tools for deeper analysis, or keep tabs on your SEO strategy's effectiveness.
44

5-
# Example Use Cases
5+
## Working with Domain Properties and Subdomains
66

7-
- **SEO Performance Report to Slack**: Automate daily or weekly SEO performance reports. Use the Google Search Console API to fetch search analytics data, then send a summary report to a Slack channel, keeping the team informed about trends, keyword rankings, and click-through rates.
7+
Google Search Console distinguishes between URL properties and Domain properties:
88

9-
- **Sync Search Results with Google Sheets**: Create a workflow that periodically pulls data from the Google Search Console API and adds it to a Google Sheet. This is useful for maintaining an evolving dataset for deeper analysis, historical reference, or sharing insights across teams without giving direct access to the Search Console.
9+
- **URL properties** are specific site URLs (e.g., `https://example.com` or `https://www.example.com`)
10+
- **Domain properties** include all subdomains and protocols (e.g., `sc-domain:example.com`)
1011

11-
- **Automatic Sitemap Submission**: Set up a Pipedream workflow that triggers whenever a new sitemap is generated in your content management system (CMS). The workflow can then automatically submit the sitemap to Google Search Console via API, ensuring Google has the latest structure of your site for crawling and indexing.
12+
When working with subdomains:
13+
14+
1. Select the domain property from the dropdown (e.g., `sc-domain:example.com`)
15+
2. Enter the subdomain URL in the "Subdomain Filter" field (e.g., `https://mcp.example.com`)
16+
3. By default, this will filter for pages containing that subdomain URL, including all subpages like `https://mcp.example.com/app/slack`
17+
18+
This approach ensures you can access subdomain data even if the subdomain isn't individually verified in Search Console.
19+
20+
### Important: Getting Data for Individual Pages
21+
22+
To see data broken down by individual pages (rather than just aggregate data):
1223

13-
## Available Actions
24+
- Add "page" to your dimensions list
25+
- This will return separate rows for each page, rather than a single aggregated row
1426

15-
[Retrieve Site Performance Data](./actions/retrieve-site-performance-data/README.md)
16-
[Submit URL for Indexing](./actions/submit-url-for-indexing/README.md)
27+
For advanced filtering needs, you can also:
1728

29+
- Change the filter dimension (page, query, country, etc.)
30+
- Change the filter operator (contains, equals, etc.)
31+
- Or use the advanced filters for complete customization
1832

33+
## Example Use Cases
34+
35+
- **SEO Performance Report to Slack**: Automate daily or weekly SEO performance reports. Use the Google Search Console API to fetch search analytics data, then send a summary report to a Slack channel, keeping the team informed about trends, keyword rankings, and click-through rates.
36+
37+
- **Sync Search Results with Google Sheets**: Create a workflow that periodically pulls data from the Google Search Console API and adds it to a Google Sheet. This is useful for maintaining an evolving dataset for deeper analysis, historical reference, or sharing insights across teams without giving direct access to the Search Console.
38+
39+
- **Automatic Sitemap Submission**: Set up a Pipedream workflow that triggers whenever a new sitemap is generated in your content management system (CMS). The workflow can then automatically submit the sitemap to Google Search Console via API, ensuring Google has the latest structure of your site for crawling and indexing.

components/google_search_console/actions/retrieve-site-performance-data/retrieve-site-performance-data.mjs

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ export default {
55
name: "Retrieve Site Performance Data",
66
description: "Fetches search analytics from Google Search Console for a verified site.",
77
key: "google_search_console-retrieve-site-performance-data",
8-
version: "0.0.2",
8+
version: "0.0.3",
99
type: "action",
1010
props: {
1111
googleSearchConsole,
1212
siteUrl: {
13-
type: "string",
14-
label: "Verified Site URL",
15-
description: "Including https:// is strongly advised",
13+
propDefinition: [
14+
googleSearchConsole,
15+
"siteUrl",
16+
],
17+
description: "Select a verified site from your Google Search Console. For subdomains, select the domain property and use dimension filters.",
1618
},
1719
startDate: {
1820
type: "string",
@@ -22,13 +24,21 @@ export default {
2224
endDate: {
2325
type: "string",
2426
label: "End Date (YYYY-MM-DD)",
25-
description: "Enddate of the range for which to retrieve site performance data",
27+
description: "End date of the range for which to retrieve site performance data",
2628
},
2729
dimensions: {
2830
type: "string[]",
2931
label: "Dimensions",
3032
optional: true,
3133
description: "e.g. ['query', 'page', 'country', 'device']",
34+
options: [
35+
"country",
36+
"device",
37+
"page",
38+
"query",
39+
"searchAppearance",
40+
"date",
41+
],
3242
},
3343
searchType: {
3444
type: "string",
@@ -68,11 +78,45 @@ export default {
6878
description: "Start row (for pagination)",
6979
optional: true,
7080
},
71-
dimensionFilterGroups: {
81+
subdomainFilter: {
82+
type: "string",
83+
label: "Subdomain Filter",
84+
optional: true,
85+
description: "Filter results to a specific subdomain when using a domain property (e.g., `https://subdomain.example.com`). This will include all subpages of the subdomain.",
86+
},
87+
filterDimension: {
88+
type: "string",
89+
label: "Filter Dimension",
90+
optional: true,
91+
description: "Dimension to filter by (defaults to page when subdomain filter is used). Using 'page' will match the subdomain and all its subpages.",
92+
options: [
93+
"country",
94+
"device",
95+
"page",
96+
"query",
97+
],
98+
default: "page",
99+
},
100+
filterOperator: {
101+
type: "string",
102+
label: "Filter Operator",
103+
optional: true,
104+
description: "Operator to use for filtering (defaults to contains when subdomain filter is used)",
105+
options: [
106+
"contains",
107+
"equals",
108+
"notContains",
109+
"notEquals",
110+
"includingRegex",
111+
"excludingRegex",
112+
],
113+
default: "contains",
114+
},
115+
advancedDimensionFilters: {
72116
type: "object",
73-
label: "Dimension Filters",
117+
label: "Advanced Dimension Filters",
74118
optional: true,
75-
description: "Follow Search Console API structure for filters",
119+
description: "For advanced use cases: custom dimension filter groups following Search Console API structure.",
76120
},
77121
dataState: {
78122
type: "string",
@@ -90,7 +134,10 @@ export default {
90134
const {
91135
googleSearchConsole,
92136
siteUrl,
93-
dimensionFilterGroups,
137+
subdomainFilter,
138+
filterDimension,
139+
filterOperator,
140+
advancedDimensionFilters,
94141
...fields
95142
} = this;
96143

@@ -102,23 +149,56 @@ export default {
102149
return acc;
103150
}, {});
104151

152+
// Build dimension filters based on user input
153+
let dimensionFilterGroups;
154+
155+
if (subdomainFilter) {
156+
// If user provided a subdomain filter, create the filter structure
157+
dimensionFilterGroups = {
158+
filterGroups: [
159+
{
160+
filters: [
161+
{
162+
dimension: filterDimension || "page",
163+
operator: filterOperator || "contains",
164+
expression: subdomainFilter,
165+
},
166+
],
167+
},
168+
],
169+
};
170+
} else if (advancedDimensionFilters) {
171+
// If user provided advanced filters, use those
172+
dimensionFilterGroups = googleSearchConsole.parseIfJsonString(advancedDimensionFilters);
173+
}
174+
105175
let response;
106176
try {
107177
response = await googleSearchConsole.getSitePerformanceData({
108178
$,
109179
url: siteUrl,
110180
data: {
111181
...body,
112-
dimensionFilterGroups: googleSearchConsole.parseIfJsonString(dimensionFilterGroups),
182+
dimensionFilterGroups,
113183
},
114184
});
115185
} catch (error) {
116186
// Identify if the error was thrown by internal validation or by the API call
117187
const thrower = googleSearchConsole.checkWhoThrewError(error);
118-
throw new Error(`Failed to fetch data ( ${thrower.whoThrew} error ) : ${error.message}. `);
119-
};
120188

121-
$.export("$summary", ` Fetched ${response.rows?.length || 0} rows of data. `);
189+
// Add more helpful error messages for common 403 errors
190+
if (error.response?.status === 403) {
191+
const message = "Access denied. If you're trying to access a subdomain, select the domain property (sc-domain:example.com) and use the subdomain filter to filter for your subdomain.";
192+
throw new Error(`Failed to fetch data: ${message}`);
193+
}
194+
195+
throw new Error(`Failed to fetch data (${thrower.whoThrew} error): ${error.message}`);
196+
}
197+
198+
const rowCount = response.rows?.length || 0;
199+
$.export("$summary", `Fetched ${rowCount} ${rowCount === 1
200+
? "row"
201+
: "rows"} of data.`);
122202
return response;
123203
},
124204
};

components/google_search_console/actions/submit-url-for-indexing/submit-url-for-indexing.mjs

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,41 @@ export default {
55
name: "Submit URL for Indexing",
66
description: "Sends a URL update notification to the Google Indexing API",
77
key: "google_search_console-submit-url-for-indexing",
8-
version: "0.0.2",
8+
version: "0.0.3",
99
type: "action",
1010
props: {
1111
googleSearchConsole,
1212
siteUrl: {
1313
type: "string",
1414
label: "URL for indexing",
15-
description: "URL to be submitted for indexing",
15+
description: "URL to be submitted for indexing (must be a canonical URL that's verified in Google Search Console)",
16+
},
17+
notificationType: {
18+
type: "string",
19+
label: "Notification Type",
20+
description: "Type of notification to send to Google",
21+
options: [
22+
{
23+
label: "URL Updated (content has been updated)",
24+
value: "URL_UPDATED",
25+
},
26+
{
27+
label: "URL Deleted (page no longer exists)",
28+
value: "URL_DELETED",
29+
},
30+
],
31+
default: "URL_UPDATED",
1632
},
1733
},
1834
async run({ $ }) {
19-
const siteUrl = trimIfString(this.siteUrl);
35+
const {
36+
siteUrl, notificationType,
37+
} = this;
38+
const trimmedUrl = trimIfString(siteUrl);
2039

2140
const warnings = [];
2241

23-
const urlCheck = this.googleSearchConsole.checkIfUrlValid(siteUrl);
42+
const urlCheck = this.googleSearchConsole.checkIfUrlValid(trimmedUrl);
2443
if (urlCheck.warnings) {
2544
warnings.push(...urlCheck.warnings);
2645
}
@@ -30,18 +49,32 @@ export default {
3049
response = await this.googleSearchConsole.submitUrlForIndexing({
3150
$,
3251
data: {
33-
url: siteUrl,
34-
type: "URL_UPDATED", // Notifies Google that the content at this URL has been updated
52+
url: trimmedUrl,
53+
type: notificationType,
3554
},
3655
});
3756
} catch (error) {
3857
const thrower = this.googleSearchConsole.checkWhoThrewError(error);
39-
throw new Error(`Failed to fetch data ( ${thrower.whoThrew} error ) : ${error.message}. ` + warnings.join("\n- "));
4058

41-
};
59+
// Add more helpful error messages for common errors
60+
if (error.response?.status === 403) {
61+
throw new Error("Access denied. Make sure the URL belongs to a property you have access to in Google Search Console.");
62+
}
63+
64+
if (error.response?.status === 400) {
65+
throw new Error("Invalid request. Ensure the URL is canonical and belongs to a verified property.");
66+
}
67+
68+
throw new Error(`Failed to submit URL (${thrower.whoThrew} error): ${error.message}`);
69+
}
70+
71+
// Format warnings string if any warnings exist
72+
const warningsString = warnings.length > 0
73+
? `\n- ${warnings.join("\n- ")}`
74+
: "";
4275

4376
// Output a summary message and any accumulated warnings
44-
$.export("$summary", ` URL submitted to Google: ${this.siteUrl}` + warnings.join("\n- "));
77+
$.export("$summary", `URL submitted to Google: ${trimmedUrl}${warningsString}`);
4578

4679
return response;
4780
},

components/google_search_console/google_search_console.app.mjs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,17 @@ import methods from "./common/methods.mjs";
44
export default {
55
type: "app",
66
app: "google_search_console",
7-
propDefinitions: {},
7+
propDefinitions: {
8+
siteUrl: {
9+
type: "string",
10+
label: "Site",
11+
description: "Select a verified site from your Search Console",
12+
async options({ prevContext }) {
13+
const { nextPageToken } = prevContext || {};
14+
return this.listSiteOptions(nextPageToken);
15+
},
16+
},
17+
},
818
methods: {
919
...methods,
1020
_makeRequest({
@@ -21,6 +31,35 @@ export default {
2131
...opts,
2232
});
2333
},
34+
async getSites(params = {}) {
35+
return this._makeRequest({
36+
method: "GET",
37+
url: "https://searchconsole.googleapis.com/webmasters/v3/sites",
38+
...params,
39+
});
40+
},
41+
async listSiteOptions(pageToken) {
42+
const params = {};
43+
if (pageToken) {
44+
params.pageToken = pageToken;
45+
}
46+
47+
const {
48+
siteEntry = [], nextPageToken,
49+
} = await this.getSites({
50+
params,
51+
});
52+
53+
return {
54+
options: siteEntry.map((site) => ({
55+
label: site.siteUrl,
56+
value: site.siteUrl,
57+
})),
58+
context: {
59+
nextPageToken,
60+
},
61+
};
62+
},
2463
getSitePerformanceData({
2564
url, ...opts
2665
}) {

components/google_search_console/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pipedream/google_search_console",
3-
"version": "0.7.1",
3+
"version": "0.8.0",
44
"description": "Pipedream google_search_console Components",
55
"main": "google_search_console.app.mjs",
66
"keywords": [

pnpm-lock.yaml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)