-
Notifications
You must be signed in to change notification settings - Fork 2
Add Google Search Console plugin #63
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| { | ||
| "name": "countryBreakdown", | ||
| "displayName": "Country breakdown", | ||
| "description": "Returns search performance metrics grouped by country", | ||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "post", | ||
| "errorHandling": { | ||
| "type": "default" | ||
| }, | ||
| "paging": { | ||
| "mode": "none" | ||
| }, | ||
| "expandInnerObjects": true, | ||
| "postBody": { | ||
| "startDate": "{{new Date(timeframe.start).toISOString().split('T')[0]}}", | ||
| "endDate": "{{new Date(timeframe.end).toISOString().split('T')[0]}}", | ||
| "dimensions": [ | ||
| "country" | ||
| ], | ||
| "rowLimit": 25000, | ||
| "dataState": "final" | ||
| }, | ||
| "postRequestScript": "postRequest/countryBreakdown.js", | ||
| "getArgs": [], | ||
| "headers": [] | ||
| }, | ||
| "providesPluginDiagnostics": true, | ||
| "timeframes": true, | ||
| "tags": [] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| { | ||
| "name": "deviceBreakdown", | ||
| "displayName": "Device breakdown", | ||
| "description": "Returns search performance metrics grouped by device type", | ||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "post", | ||
| "errorHandling": { | ||
| "type": "default" | ||
| }, | ||
| "paging": { | ||
| "mode": "none" | ||
| }, | ||
| "expandInnerObjects": true, | ||
| "postBody": { | ||
| "startDate": "{{new Date(timeframe.start).toISOString().split('T')[0]}}", | ||
| "endDate": "{{new Date(timeframe.end).toISOString().split('T')[0]}}", | ||
| "dimensions": [ | ||
| "device" | ||
| ], | ||
| "rowLimit": 25000, | ||
| "dataState": "final" | ||
| }, | ||
| "postRequestScript": "postRequest/deviceBreakdown.js", | ||
| "getArgs": [], | ||
| "headers": [] | ||
| }, | ||
| "providesPluginDiagnostics": true, | ||
| "timeframes": true, | ||
| "tags": [] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| { | ||
| "name": "pageDistribution", | ||
| "displayName": "Page distribution", | ||
| "description": "Returns a distribution of queries by search ranking position for the selected page", | ||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "post", | ||
| "errorHandling": { | ||
| "type": "default" | ||
| }, | ||
| "paging": { | ||
| "mode": "none" | ||
| }, | ||
| "expandInnerObjects": true, | ||
| "getArgs": [], | ||
| "headers": [], | ||
| "postBody": { | ||
| "startDate": "{{new Date(timeframe.start).toISOString().split('T')[0]}}", | ||
| "endDate": "{{new Date(timeframe.end).toISOString().split('T')[0]}}", | ||
| "dimensions": [ | ||
| "query" | ||
| ], | ||
| "dimensionFilterGroups": [ | ||
| { | ||
| "filters": [ | ||
| { | ||
| "dimension": "page", | ||
| "operator": "equals", | ||
| "expression": "{{scope?.[0]?.name}}" | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "rowLimit": 25000 | ||
| }, | ||
| "postRequestScript": "postRequest/pageDistribution.js" | ||
| }, | ||
| "providesPluginDiagnostics": true, | ||
| "timeframes": true, | ||
| "tags": [], | ||
|
|
||
| "ui": [ | ||
| { | ||
| "name": "scope", | ||
| "objectLimit": 1, | ||
| "label": "Scope", | ||
| "type": "objects", | ||
| "matches": { | ||
| "sourceType": { | ||
| "type": "equals", | ||
| "value": "gsc-page" | ||
| } | ||
| }, | ||
| "validation": { | ||
| "required": true | ||
| } | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| { | ||
| "name": "pagePerformanceOverTime", | ||
| "displayName": "Page performance over time", | ||
| "description": "Returns daily clicks, impressions and CTR trends for the selected page", | ||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "post", | ||
| "errorHandling": { | ||
| "type": "default" | ||
| }, | ||
| "paging": { | ||
| "mode": "none" | ||
| }, | ||
| "expandInnerObjects": true, | ||
| "postBody": { | ||
| "startDate": "{{new Date(timeframe.start).toISOString().split('T')[0]}}", | ||
| "endDate": "{{new Date(timeframe.end).toISOString().split('T')[0]}}", | ||
| "dimensions": [ | ||
| "date" | ||
| ], | ||
| "dimensionFilterGroups": [ | ||
| { | ||
| "filters": [ | ||
| { | ||
| "dimension": "page", | ||
| "operator": "equals", | ||
| "expression": "{{scope?.[0]?.name}}" | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "rowLimit": 25000 | ||
| }, | ||
| "postRequestScript": "postRequest/pagePerformanceOverTime.js", | ||
| "getArgs": [], | ||
| "headers": [] | ||
| }, | ||
| "providesPluginDiagnostics": true, | ||
| "timeframes": true, | ||
| "tags": [], | ||
|
|
||
| "ui": [ | ||
| { | ||
| "name": "scope", | ||
| "objectLimit": 1, | ||
| "label": "Scope", | ||
| "type": "objects", | ||
| "matches": { | ||
| "sourceType": { | ||
| "type": "equals", | ||
| "value": "gsc-page" | ||
| } | ||
| }, | ||
| "validation": { | ||
| "required": true | ||
| } | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| { | ||
| "name": "pagePerformanceSummary", | ||
| "displayName": "Page performance summary", | ||
| "description": "Returns performance metrics for a given page", | ||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "post", | ||
| "errorHandling": { | ||
| "type": "default" | ||
| }, | ||
| "paging": { | ||
| "mode": "none" | ||
| }, | ||
| "expandInnerObjects": true, | ||
| "getArgs": [], | ||
| "headers": [], | ||
| "postBody": { | ||
| "startDate": "{{new Date(timeframe.start).toISOString().split('T')[0]}}", | ||
| "endDate": "{{new Date(timeframe.end).toISOString().split('T')[0]}}", | ||
| "dimensions": [ | ||
| "page" | ||
| ], | ||
| "dimensionFilterGroups": [ | ||
| { | ||
| "filters": [ | ||
| { | ||
| "dimension": "page", | ||
| "operator": "equals", | ||
| "expression": "{{scope?.[0]?.name}}" | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "rowLimit": 25000 | ||
| }, | ||
| "postRequestScript": "postRequest/script1.js" | ||
| }, | ||
| "providesPluginDiagnostics": true, | ||
| "timeframes": true, | ||
| "tags": [], | ||
|
|
||
| "ui": [ | ||
| { | ||
| "name": "scope", | ||
| "objectLimit": 1, | ||
| "label": "Scope", | ||
| "type": "objects", | ||
| "matches": { | ||
| "sourceType": { | ||
| "type": "equals", | ||
| "value": "gsc-page" | ||
| } | ||
| }, | ||
| "validation": { | ||
| "required": true | ||
| } | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| { | ||
| "name": "pageUrLs", | ||
| "displayName": "Page URLs", | ||
| "description": "Returns a list off URLs with impressions in the last year", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't look quite right - the timeframe is 'now -> whatever end date selected in SquaredUp'
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This stream is used for object discovery / indexing where I wanted to pull pages seen in the last year regardless of the dashboard timeframe (the API always requires a You reckon I need to update it somehow or is it okay for that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Typo in the user-facing description: line 4 reads Extended reasoning...What the bug is
"description": "Returns a list off URLs with impressions in the last year","a list off URLs" should be "a list of URLs". A small but plainly wrong typo. Why it mattersThe How it manifests
There's no code path that filters or sanitises this string — it is rendered as-is from the JSON. Why existing code doesn't prevent itPlugin validation ( How to fixOne-character edit: - "description": "Returns a list off URLs with impressions in the last year",
+ "description": "Returns a list of URLs with impressions in the last year",SeverityPure cosmetic typo, no functional impact — |
||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "post", | ||
| "errorHandling": { | ||
| "type": "default" | ||
| }, | ||
| "paging": { | ||
| "mode": "none" | ||
| }, | ||
| "expandInnerObjects": true, | ||
| "postBody": { | ||
| "startDate": "{{new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]}}", | ||
| "endDate": "{{new Date().toISOString().split('T')[0]}}", | ||
| "dimensions": [ | ||
| "page" | ||
| ], | ||
| "rowLimit": 25 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do this or other streams need paging configured?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't need explicit paging but I definitively need to check the |
||
| }, | ||
|
Comment on lines
+15
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 The Extended reasoning...What the bug is
Why this matters more than for any other stream
So Step-by-step proof for a realistic site (say 200 indexed pages)
Why existing code doesn't prevent thisThere's no auto-paging on this stream ( Author has already confirmedIn inline comment 3428036489 the author wrote: "Doesn't need explicit paging but I definitively need to check the rowLimit configs, for one this stream should be way higher than 25!" — so this is already acknowledged as unintended, but the diff still shows How to fixChange line 21 of 🔬 also observed by verify-runtime |
||
| "postRequestScript": "postRequest/pageUrLs.js", | ||
| "getArgs": [], | ||
| "headers": [] | ||
| }, | ||
| "timeframes": false, | ||
| "providesPluginDiagnostics": true, | ||
| "tags": [] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| { | ||
| "name": "pages", | ||
| "displayName": "Pages", | ||
| "description": "Returns a complete list of pages with performance metrics", | ||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "post", | ||
| "errorHandling": { | ||
| "type": "default" | ||
| }, | ||
| "paging": { | ||
| "mode": "none" | ||
| }, | ||
| "expandInnerObjects": true, | ||
| "postBody": { | ||
| "startDate": "{{new Date(timeframe.start).toISOString().split('T')[0]}}", | ||
| "endDate": "{{new Date(timeframe.end).toISOString().split('T')[0]}}", | ||
| "dimensions": [ | ||
| "page" | ||
| ], | ||
| "rowLimit": 25000 | ||
| }, | ||
| "postRequestScript": "postRequest/pages.js", | ||
| "getArgs": [], | ||
| "headers": [] | ||
| }, | ||
| "providesPluginDiagnostics": true, | ||
| "timeframes": true, | ||
| "tags": [] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| { | ||
| "name": "previousPeriodPagePerformance", | ||
| "displayName": "Previous period page performance", | ||
| "description": "Returns a summary of performance for the previous period of a given timeframe for the selected page", | ||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "post", | ||
| "errorHandling": { | ||
| "type": "default" | ||
| }, | ||
| "paging": { | ||
| "mode": "none" | ||
| }, | ||
| "expandInnerObjects": true, | ||
| "getArgs": [], | ||
| "headers": [], | ||
| "postBody": { | ||
| "startDate": "{{new Date(new Date(timeframe.start).getTime() - (new Date(timeframe.end).getTime() - new Date(timeframe.start).getTime()) - 86400000).toISOString().split('T')[0]}}", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't quite get my head around what happens if selected yearly ranges or something here - do we need to restrict the available timeframes?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don’t think we need to restrict timeframes, as the calculation should work within 1 day error - I guess the main consideration is performance but I've not been able to test with a large dataset (and GSC data has a max query timeframe of 16 months) |
||
| "endDate": "{{new Date(new Date(timeframe.start).getTime() - 86400000).toISOString().split('T')[0]}}", | ||
| "dimensions": ["page"], | ||
| "dimensionFilterGroups": [ | ||
| { | ||
| "filters": [ | ||
| { | ||
| "dimension": "page", | ||
| "operator": "equals", | ||
| "expression": "{{scope?.[0]?.name}}" | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "rowLimit": 25000 | ||
| }, | ||
| "postRequestScript": "postRequest/script1.js" | ||
| }, | ||
| "timeframes": true, | ||
| "providesPluginDiagnostics": true, | ||
| "tags": [], | ||
| "visibility": { | ||
| "type": "hidden" | ||
| }, | ||
| "ui": [ | ||
| { | ||
| "name": "scope", | ||
| "objectLimit": 1, | ||
| "label": "Scope", | ||
| "type": "objects", | ||
| "matches": { | ||
| "sourceType": { | ||
| "type": "equals", | ||
| "value": "gsc-page" | ||
| } | ||
| }, | ||
| "validation": { | ||
| "required": true | ||
| } | ||
| } | ||
| ] | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 All 14 new data stream JSON files in this PR ship with
"tags": [](e.g. countryBreakdown.json:30, pages.json:29, queries.json, sitePerformanceOverTime.json, etc.). The repo review guidelines explicitly statetagsare mandatory and should reuse an existing category from other plugins where possible — peer plugins like GoogleSheets, Huntress, and AutoTask all populate them. Consider something like["SEO", "Analytics"]across all 14 streams to aid discoverability.Extended reasoning...
What the bug is
The repo review guidelines for plugin authors (see the
REVIEW.md/ contribution guidance applied across this repo) are explicit:Every one of the 14 new data stream JSON files added in this PR ships with an empty
tagsarray:plugins/GoogleSearchConsole/v1/dataStreams/countryBreakdown.json:30→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/deviceBreakdown.json:30→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/pageDistribution.json→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/pagePerformanceOverTime.json→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/pagePerformanceSummary.json→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/pageUrLs.json:29→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/pages.json:29→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/previousPeriodPagePerformance.json→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/previousPeriodPerformanceOverTime.json→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/previousPeriodQueriesByPage.json→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/queries.json→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/queriesByPage.json→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/sitePerformanceOverTime.json→"tags": []plugins/GoogleSearchConsole/v1/dataStreams/sitePerformanceSummary.json→"tags": []Comparison to peer plugins
A quick comparison against other plugins in the repo confirms the convention is widely followed:
plugins/Huntress/v1/dataStreams/*.jsonpopulates tags such as["Security", "Incidents"].plugins/AutoTask/v1/dataStreams/*.jsonuses tags like["Tickets"],["Resources"].plugins/Checkly,plugins/GoogleSheets,plugins/NinjaOne, and others all populatetagswith descriptive categories such as["Monitoring"],["Security"],["Backup"],["Ticketing"].The GSC plugin is essentially the only PR shipping uniformly empty tag arrays across every data stream.
Step-by-step proof of impact
"tags": [], none of them show up under any category — they are only discoverable by exact name.Securitysurfaces all of its data streams immediately.Why existing code does not prevent it
The schema does not reject empty arrays —
MicrosoftDefender(added in commitefb12f9) also ships with empty tags, so prior precedent shows the validator does not enforce this. The guideline is documented in the review process rather than the schema, which is why it slipped through.Impact
No functional impact — data streams still execute correctly and dashboards still render. The cost is purely discoverability: users browsing the plugin catalogue by category will not find these streams under any SEO/Analytics tag, and the plugin will look inconsistent next to peers that follow the convention.
How to fix
A one-line edit in each of the 14 files. Suggested values (reusing categories used elsewhere in the repo):
or, if
SEOis too narrow as a single tag,["Analytics"]alone (the plugin already uses"category": "Analytics"inmetadata.json, so reusing that here is consistent with the existing self-categorisation).