diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9690e211..932be760 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -24,6 +24,7 @@ plugins/TransportForLondon/* @clarkd plugins/UniFi/* @adamkinniburgh plugins/UptimeRobot/* @kieranlangton plugins/WorldCup2026/* @TimWheeler-SQUP +plugins/Algolia/* @andrewmumblebee # Fallback – if a plugin has no specified author diff --git a/plugins/Algolia/v1/configValidation.json b/plugins/Algolia/v1/configValidation.json new file mode 100644 index 00000000..4bd9fd7e --- /dev/null +++ b/plugins/Algolia/v1/configValidation.json @@ -0,0 +1,11 @@ +{ + "steps": [ + { + "displayName": "Authenticate", + "dataStream": { "name": "indexes" }, + "required": true, + "error": "Could not connect to Algolia. Check your Application ID is correct and that your API key has the 'listIndexes' ACL (a Search-Only key will not work).", + "success": "Connected to Algolia." + } + ] +} diff --git a/plugins/Algolia/v1/custom_types.json b/plugins/Algolia/v1/custom_types.json new file mode 100644 index 00000000..845c2dd0 --- /dev/null +++ b/plugins/Algolia/v1/custom_types.json @@ -0,0 +1,9 @@ +[ + { + "name": "Index", + "sourceType": "Index", + "icon": "magnifying-glass", + "singular": "Index", + "plural": "Indices" + } +] diff --git a/plugins/Algolia/v1/dataStreams/clickThroughRate.json b/plugins/Algolia/v1/dataStreams/clickThroughRate.json new file mode 100644 index 00000000..917fcf01 --- /dev/null +++ b/plugins/Algolia/v1/dataStreams/clickThroughRate.json @@ -0,0 +1,56 @@ +{ + "name": "clickThroughRate", + "displayName": "Click-through Rate", + "description": "Retrieves the click-through rate (CTR) for all your searches with at least one click event", + "tags": ["Analytics"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/2/clicks/clickThroughRate", + "getArgs": [ + { "key": "index", "value": "{{object.rawId}}" }, + { + "key": "startDate", + "value": "{{timeframeUtc.start.split('T')[0]}}" + }, + { + "key": "endDate", + "value": "{{timeframeUtc.end.split('T')[0]}}" + } + ], + "postRequestScript": "datesAddIndex.js", + "paging": { "mode": "none" } + }, + "matches": { "sourceType": { "type": "equals", "value": "Index" } }, + "metadata": [ + { + "name": "index", + "displayName": "Index", + "shape": "string", + "role": "id" + }, + { + "name": "date", + "displayName": "Date", + "shape": "date", + "role": "timestamp" + }, + { + "name": "rate", + "displayName": "Click-through Rate", + "shape": ["percent", { "asZeroToOne": true, "decimalPlaces": 0 }], + "role": "value" + }, + { + "name": "clickCount", + "displayName": "Click Count", + "shape": ["number", { "decimalPlaces": 0 }] + }, + { + "name": "trackedSearchCount", + "displayName": "Tracked Search Count", + "shape": ["number", { "decimalPlaces": 0 }] + } + ], + "timeframes": true +} diff --git a/plugins/Algolia/v1/dataStreams/conversionRate.json b/plugins/Algolia/v1/dataStreams/conversionRate.json new file mode 100644 index 00000000..f9eab488 --- /dev/null +++ b/plugins/Algolia/v1/dataStreams/conversionRate.json @@ -0,0 +1,56 @@ +{ + "name": "conversionRate", + "displayName": "Conversion Rate", + "description": "Retrieves the conversion rate (CR) for all your searches with at least one conversion event", + "tags": ["Analytics"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/2/conversions/conversionRate", + "getArgs": [ + { "key": "index", "value": "{{object.rawId}}" }, + { + "key": "startDate", + "value": "{{timeframeUtc.start.split('T')[0]}}" + }, + { + "key": "endDate", + "value": "{{timeframeUtc.end.split('T')[0]}}" + } + ], + "postRequestScript": "datesAddIndex.js", + "paging": { "mode": "none" } + }, + "matches": { "sourceType": { "type": "equals", "value": "Index" } }, + "metadata": [ + { + "name": "index", + "displayName": "Index", + "shape": "string", + "role": "id" + }, + { + "name": "date", + "displayName": "Date", + "shape": "date", + "role": "timestamp" + }, + { + "name": "rate", + "displayName": "Conversion Rate", + "shape": ["percent", { "asZeroToOne": true, "decimalPlaces": 0 }], + "role": "value" + }, + { + "name": "conversionCount", + "displayName": "Conversion Count", + "shape": ["number", { "decimalPlaces": 0 }] + }, + { + "name": "trackedSearchCount", + "displayName": "Tracked Search Count", + "shape": ["number", { "decimalPlaces": 0 }] + } + ], + "timeframes": true +} diff --git a/plugins/Algolia/v1/dataStreams/indexes.json b/plugins/Algolia/v1/dataStreams/indexes.json new file mode 100644 index 00000000..2a05028a --- /dev/null +++ b/plugins/Algolia/v1/dataStreams/indexes.json @@ -0,0 +1,55 @@ +{ + "name": "indexes", + "displayName": "Indices", + "description": "Algolia indices in the application, with record counts, sizes, last build time and pending tasks", + "tags": ["Indices"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "baseUrl": "https://{{dataSource.applicationId}}.algolia.net", + "httpMethod": "get", + "endpointPath": "/1/indexes", + "pathToData": "items", + "paging": { "mode": "none" } + }, + "matches": "none", + "metadata": [ + { "name": "name", "displayName": "Index", "role": "label" }, + { + "name": "entries", + "displayName": "Records", + "shape": ["number", { "decimalPlaces": 0 }] + }, + { + "name": "dataSize", + "displayName": "Data Size", + "shape": ["bytes", { "thousandsSeparator": true }] + }, + { + "name": "fileSize", + "displayName": "File Size", + "shape": ["bytes", { "thousandsSeparator": true }] + }, + { + "name": "lastBuildTimeS", + "displayName": "Last Build Time", + "shape": ["seconds", { "thousandsSeparator": true }] + }, + { + "name": "numberOfPendingTasks", + "displayName": "Pending Tasks", + "shape": ["number", { "decimalPlaces": 0 }] + }, + { + "name": "pendingTask", + "displayName": "Has Pending Task", + "shape": "boolean" + }, + { "name": "updatedAt", "displayName": "Last Updated", "shape": "date" }, + { + "name": "primary", + "displayName": "Primary Index", + "shape": "boolean" + } + ], + "timeframes": false +} diff --git a/plugins/Algolia/v1/dataStreams/noResultRate.json b/plugins/Algolia/v1/dataStreams/noResultRate.json new file mode 100644 index 00000000..2d444f32 --- /dev/null +++ b/plugins/Algolia/v1/dataStreams/noResultRate.json @@ -0,0 +1,52 @@ +{ + "name": "noResultRate", + "displayName": "No-Result Rate", + "description": "Daily proportion of searches returning no results for an index", + "tags": ["Analytics"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/2/searches/noResultRate", + "getArgs": [ + { "key": "index", "value": "{{object.rawId}}" }, + { + "key": "startDate", + "value": "{{timeframeUtc.start.split('T')[0]}}" + }, + { "key": "endDate", "value": "{{timeframeUtc.end.split('T')[0]}}" } + ], + "postRequestScript": "datesAddIndex.js" + }, + "matches": { "sourceType": { "type": "equals", "value": "Index" } }, + "metadata": [ + { + "name": "index", + "displayName": "Index", + "shape": "string", + "role": "id" + }, + { + "name": "date", + "displayName": "Date", + "shape": "date", + "role": "timestamp" + }, + { + "name": "rate", + "displayName": "No-result rate", + "shape": ["percent", { "asZeroToOne": true, "decimalPlaces": 0 }], + "role": "value" + }, + { + "name": "count", + "displayName": "Searches", + "shape": ["number", { "decimalPlaces": 0 }] + }, + { + "name": "noResultCount", + "displayName": "No-result searches", + "shape": ["number", { "decimalPlaces": 0 }] + } + ], + "timeframes": true +} diff --git a/plugins/Algolia/v1/dataStreams/noResultSearches.json b/plugins/Algolia/v1/dataStreams/noResultSearches.json new file mode 100644 index 00000000..a9dbd91b --- /dev/null +++ b/plugins/Algolia/v1/dataStreams/noResultSearches.json @@ -0,0 +1,48 @@ +{ + "name": "noResultSearches", + "displayName": "Top Searches With No Results", + "description": "Most frequent searches that returned no results for an index", + "tags": ["Analytics"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/2/searches/noResults", + "getArgs": [ + { "key": "index", "value": "{{object.rawId}}" }, + { + "key": "startDate", + "value": "{{timeframeUtc.start.split('T')[0]}}" + }, + { "key": "endDate", "value": "{{timeframeUtc.end.split('T')[0]}}" }, + { "key": "limit", "value": "50" } + ], + "postRequestScript": "searchAddIndex.js", + "paging": { "mode": "none" } + }, + "matches": { "sourceType": { "type": "equals", "value": "Index" } }, + "metadata": [ + { + "name": "index", + "displayName": "Index", + "shape": "string", + "role": "id" + }, + { + "name": "search", + "displayName": "Search", + "shape": "string", + "role": "label" + }, + { + "name": "count", + "displayName": "Count", + "shape": ["number", { "decimalPlaces": 0 }] + }, + { + "name": "withFilterCount", + "displayName": "With filters", + "shape": ["number", { "decimalPlaces": 0 }] + } + ], + "timeframes": true +} diff --git a/plugins/Algolia/v1/dataStreams/scripts/datesAddIndex.js b/plugins/Algolia/v1/dataStreams/scripts/datesAddIndex.js new file mode 100644 index 00000000..6c9f6131 --- /dev/null +++ b/plugins/Algolia/v1/dataStreams/scripts/datesAddIndex.js @@ -0,0 +1,8 @@ +// Add the index name to allow grouping +var object = context.objects[0]; +result = + data?.dates?.map((r) => ({ + ...r, + index: object.rawId, + })) ?? []; + diff --git a/plugins/Algolia/v1/dataStreams/scripts/searchAddIndex.js b/plugins/Algolia/v1/dataStreams/scripts/searchAddIndex.js new file mode 100644 index 00000000..e9ea0197 --- /dev/null +++ b/plugins/Algolia/v1/dataStreams/scripts/searchAddIndex.js @@ -0,0 +1,8 @@ +// Add the index name to allow grouping +var object = context.objects[0]; +result = + data?.searches?.map((r) => ({ + ...r, + index: object.rawId, + })) ?? []; + diff --git a/plugins/Algolia/v1/dataStreams/scripts/topCountries.js b/plugins/Algolia/v1/dataStreams/scripts/topCountries.js new file mode 100644 index 00000000..fd494817 --- /dev/null +++ b/plugins/Algolia/v1/dataStreams/scripts/topCountries.js @@ -0,0 +1,8 @@ +// Add the index name to allow grouping +var object = context.objects[0]; +result = + data?.countries?.map((r) => ({ + ...r, + index: object.rawId, + })) ?? []; + diff --git a/plugins/Algolia/v1/dataStreams/searchCount.json b/plugins/Algolia/v1/dataStreams/searchCount.json new file mode 100644 index 00000000..1fed8081 --- /dev/null +++ b/plugins/Algolia/v1/dataStreams/searchCount.json @@ -0,0 +1,45 @@ +{ + "name": "searchCount", + "displayName": "Search Count", + "description": "Daily number of searches for an index over the selected timeframe", + "tags": ["Analytics"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/2/searches/count", + "getArgs": [ + { "key": "index", "value": "{{object.rawId}}" }, + { + "key": "startDate", + "value": "{{timeframeUtc.start.split('T')[0]}}" + }, + { "key": "endDate", "value": "{{timeframeUtc.end.split('T')[0]}}" } + ], + "postRequestScript": "datesAddIndex.js" + }, + "matches": { "sourceType": { "type": "equals", "value": "Index" } }, + "metadata": [ + { + "name": "index", + "displayName": "Index", + "shape": "string", + "role": "id" + }, + { + "name": "date", + "displayName": "Date", + "shape": "date", + "role": "timestamp" + }, + { + "name": "count", + "displayName": "Searches", + "shape": ["number", { "decimalPlaces": 0 }], + "role": "value" + }, + { + "pattern": ".*" + } + ], + "timeframes": true +} diff --git a/plugins/Algolia/v1/dataStreams/topCountries.json b/plugins/Algolia/v1/dataStreams/topCountries.json new file mode 100644 index 00000000..195e09f8 --- /dev/null +++ b/plugins/Algolia/v1/dataStreams/topCountries.json @@ -0,0 +1,44 @@ +{ + "name": "topCountries", + "displayName": "Top Countries", + "description": "Retrieves the countries with the most searches in your index", + "tags": ["Analytics"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/2/countries", + "getArgs": [ + { "key": "index", "value": "{{object.rawId}}" }, + { + "key": "startDate", + "value": "{{timeframeUtc.start.split('T')[0]}}" + }, + { "key": "endDate", "value": "{{timeframeUtc.end.split('T')[0]}}" }, + { "key": "limit", "value": "50" } + ], + "postRequestScript": "topCountries.js", + "paging": { "mode": "none" } + }, + "matches": { "sourceType": { "type": "equals", "value": "Index" } }, + "metadata": [ + { + "name": "index", + "displayName": "Index", + "shape": "string", + "role": "id" + }, + { + "name": "country", + "displayName": "Country", + "shape": "string", + "role": "label" + }, + { + "name": "count", + "displayName": "Count", + "shape": ["number", { "decimalPlaces": 0 }], + "role": "value" + } + ], + "timeframes": true +} diff --git a/plugins/Algolia/v1/dataStreams/topSearches.json b/plugins/Algolia/v1/dataStreams/topSearches.json new file mode 100644 index 00000000..2666fa13 --- /dev/null +++ b/plugins/Algolia/v1/dataStreams/topSearches.json @@ -0,0 +1,51 @@ +{ + "name": "topSearches", + "displayName": "Top Searches", + "description": "Most frequent search queries for an index over the selected timeframe", + "tags": ["Analytics"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/2/searches", + "getArgs": [ + { "key": "index", "value": "{{object.rawId}}" }, + { + "key": "startDate", + "value": "{{timeframeUtc.start.split('T')[0]}}" + }, + { + "key": "endDate", + "value": "{{timeframeUtc.end.split('T')[0]}}" + }, + { "key": "limit", "value": "50" } + ], + "postRequestScript": "searchAddIndex.js", + "paging": { "mode": "none" } + }, + "matches": { "sourceType": { "type": "equals", "value": "Index" } }, + "metadata": [ + { + "name": "index", + "displayName": "Index", + "shape": "string", + "role": "id" + }, + { + "name": "search", + "displayName": "Search", + "shape": "string", + "role": "label" + }, + { + "name": "count", + "displayName": "Count", + "shape": ["number", { "decimalPlaces": 0 }] + }, + { + "name": "nbHits", + "displayName": "Results", + "shape": ["number", { "decimalPlaces": 0 }] + } + ], + "timeframes": true +} diff --git a/plugins/Algolia/v1/dataStreams/topUsers.json b/plugins/Algolia/v1/dataStreams/topUsers.json new file mode 100644 index 00000000..4dfd73f6 --- /dev/null +++ b/plugins/Algolia/v1/dataStreams/topUsers.json @@ -0,0 +1,43 @@ +{ + "name": "numberOfUsers", + "displayName": "Number of users", + "description": "Retrieves the number of unique users for an index over the selected timeframe", + "tags": ["Analytics"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/2/users/count", + "getArgs": [ + { "key": "index", "value": "{{object.rawId}}" }, + { + "key": "startDate", + "value": "{{timeframeUtc.start.split('T')[0]}}" + }, + { "key": "endDate", "value": "{{timeframeUtc.end.split('T')[0]}}" } + ], + "postRequestScript": "datesAddIndex.js", + "paging": { "mode": "none" } + }, + "matches": { "sourceType": { "type": "equals", "value": "Index" } }, + "metadata": [ + { + "name": "index", + "displayName": "Index", + "shape": "string", + "role": "id" + }, + { + "name": "date", + "displayName": "Date", + "shape": "date", + "role": "timestamp" + }, + { + "name": "count", + "displayName": "Count", + "shape": ["number", { "decimalPlaces": 0 }], + "role": "value" + } + ], + "timeframes": true +} diff --git a/plugins/Algolia/v1/defaultContent/index.dash.json b/plugins/Algolia/v1/defaultContent/index.dash.json new file mode 100644 index 00000000..0ec84ee9 --- /dev/null +++ b/plugins/Algolia/v1/defaultContent/index.dash.json @@ -0,0 +1,462 @@ +{ + "name": "Index", + "schemaVersion": "1.5", + "timeframe": "last7days", + "variables": ["{{variables.[Algolia Index]}}"], + "dashboard": { + "_type": "layout/grid", + "version": 1, + "contents": [ + { + "w": 1, + "h": 2, + "x": 0, + "y": 0, + "z": 0, + "i": "d5bc5bb9-7ef6-400d-9891-453a961e3df7", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "searchCount", + "id": "{{dataStreams.[searchCount]}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "sum", + "names": ["count"] + } + ] + } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "comparisonColumn": "none", + "label": "Total searches", + "value": "count_sum", + "labelPosition": "bottom" + } + } + } + } + }, + { + "w": 1, + "h": 2, + "x": 1, + "y": 0, + "z": 0, + "i": "cc9cf8e5-39ca-45eb-beb3-bc1b18985ea4", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "conversionRate", + "id": "{{dataStreams.[conversionRate]}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "mean", + "names": ["rate"] + } + ] + }, + "metadata": [ + { + "shape": [ + "percent", + { + "thousandsSeparator": true, + "decimalPlaces": 0, + "asZeroToOne": true + } + ], + "name": "rate_mean" + } + ] + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "", + "visualisation": { + "config": { + "data-stream-scalar": { + "value": "rate_mean", + "comparisonColumn": "none", + "label": "Conversion Rate" + } + }, + "type": "data-stream-scalar" + } + } + }, + { + "w": 1, + "h": 2, + "x": 2, + "y": 0, + "z": 0, + "i": "39fcefbc-ad87-4c4a-bf57-0567e906d83a", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "noResultRate", + "metadata": [ + { + "shape": [ + "percent", + { + "thousandsSeparator": true, + "decimalPlaces": 0, + "asZeroToOne": true + } + ], + "name": "rate_mean" + } + ], + "id": "{{dataStreams.[noResultRate]}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "mean", + "names": ["rate"] + } + ] + } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "rate_mean", + "comparisonColumn": "none", + "label": "No-result rate" + } + } + } + } + }, + { + "w": 1, + "h": 2, + "x": 3, + "y": 0, + "z": 0, + "i": "aad2ae3b-bfad-45a9-b416-c1ce40588b52", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "clickThroughRate", + "metadata": [ + { + "shape": [ + "percent", + { + "thousandsSeparator": true, + "decimalPlaces": 0, + "asZeroToOne": true + } + ], + "name": "rate_mean" + } + ], + "id": "{{dataStreams.[clickThroughRate]}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "mean", + "names": ["rate"] + } + ] + } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "rate_mean", + "comparisonColumn": "none", + "label": "Click-through rate" + } + } + } + } + }, + { + "w": 2, + "h": 4, + "x": 0, + "y": 2, + "z": 0, + "i": "1c816936-aafc-42dc-8096-10f0281a6e17", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "topSearches", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[topSearches]}}", + "sort": { + "by": [["count", "desc"]] + } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "The most popular search queries with how many results they return", + "activePluginConfigIds": ["{{configId}}"], + "baseTile": "data-stream-base-tile", + "title": "Top Searches", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["search", "count", "nbHits"], + "hiddenColumns": ["index"], + "resizedColumns": { + "columnWidths": { + "search": 448 + } + }, + "transpose": false + } + } + } + } + }, + { + "w": 2, + "h": 4, + "x": 2, + "y": 2, + "z": 0, + "i": "1727a100-9c88-41cb-b157-c0f197b1b13e", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "noResultSearches", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[noResultSearches]}}", + "sort": { + "by": [["count", "desc"]] + } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "The following queries are not returning any results", + "activePluginConfigIds": ["{{configId}}"], + "title": "Searches With No Results", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["search", "count"], + "hiddenColumns": ["index"], + "transpose": false + } + } + } + } + }, + { + "w": 2, + "h": 3, + "x": 0, + "y": 6, + "z": 0, + "i": "dfa41039-1657-4142-b6a5-16ac7abffb9d", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "searchCount", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[searchCount]}}", + "sort": { + "by": [["date_byDay", "asc"]] + }, + "group": { + "by": [["date", "byDay"]], + "aggregate": [ + { + "type": "sum", + "names": ["count"] + } + ] + } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Search Volume", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "yAxisLabel": "Searches", + "xAxisColumn": "date_byDay", + "showLegend": false, + "showYAxisLabel": true, + "seriesColumn": "none", + "showTrendLine": false, + "legendPosition": "bottom", + "yAxisColumn": ["count_sum"] + } + } + } + } + }, + { + "w": 2, + "h": 3, + "x": 2, + "y": 6, + "z": 0, + "i": "09cc7897-4e96-44b3-9205-144fcf3ef5fb", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "noResultRate", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[noResultRate]}}", + "sort": { + "by": [["date_byDay", "asc"]] + }, + "group": { + "by": [["date", "byDay"]], + "aggregate": [ + { + "type": "mean", + "names": ["rate"] + } + ] + } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "No-Result Rate", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "yAxisLabel": "No-Result Rate", + "xAxisColumn": "date_byDay", + "showLegend": false, + "showYAxisLabel": true, + "seriesColumn": "none", + "showTrendLine": false, + "legendPosition": "bottom", + "yAxisColumn": ["rate_mean"] + } + } + } + } + }, + { + "w": 4, + "h": 4, + "x": 0, + "y": 9, + "z": 0, + "i": "9b9fc2b9-c56d-47de-a4c4-d151e467a210", + "moved": false, + "static": false, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "pluginConfigId": "{{configId}}", + "id": "datastream-properties" + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Index Details", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true + } + } + } + } + } + ], + "columns": 4 + } +} diff --git a/plugins/Algolia/v1/defaultContent/manifest.json b/plugins/Algolia/v1/defaultContent/manifest.json new file mode 100644 index 00000000..c6cec2d6 --- /dev/null +++ b/plugins/Algolia/v1/defaultContent/manifest.json @@ -0,0 +1,6 @@ +{ + "items": [ + { "name": "overview", "type": "dashboard" }, + { "name": "index", "type": "dashboard" } + ] +} diff --git a/plugins/Algolia/v1/defaultContent/overview.dash.json b/plugins/Algolia/v1/defaultContent/overview.dash.json new file mode 100644 index 00000000..08f2bfcb --- /dev/null +++ b/plugins/Algolia/v1/defaultContent/overview.dash.json @@ -0,0 +1,335 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "timeframe": "last7days", + "variables": [], + "dashboard": { + "_type": "layout/grid", + "version": 50, + "columns": 4, + "contents": [ + { + "i": "66e90a23-50f4-4e6b-b6b1-b8036378e1f7", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total Indices", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[indexes]}}", + "name": "indexes", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [{ "type": "count" }] + } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Total Indices" + } + } + } + } + }, + { + "i": "08ad1e7c-f8c3-42b4-ab41-b8286aba64bc", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total Records", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[indexes]}}", + "name": "indexes", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { "type": "sum", "names": ["entries"] } + ] + } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "entries_sum", + "comparisonColumn": "none", + "label": "Total Records" + } + } + } + } + }, + { + "i": "c3f45e29-63aa-4d33-a4b8-1eab017dacf6", + "x": 2, + "y": 0, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Search Count", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[searchCount]}}", + "name": "searchCount", + "group": { + "by": [["index", "uniqueValues"]], + "aggregate": [{ "type": "sum", "names": ["count"] }] + } + }, + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).has(\"sourceType\", \"Index\").limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count_sum", + "labelColumn": "index_uniqueValues", + "legendMode": "inline", + "legendPosition": "auto", + "showValuesAsPercentage": false, + "hideCenterValue": false + } + } + } + } + }, + { + "i": "6d24028f-a3dd-44f1-93dd-417a1c4e9863", + "x": 0, + "y": 2, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Top Indices by Records", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[indexes]}}", + "name": "indexes", + "pluginConfigId": "{{configId}}", + "group": { + "by": [["name", "uniqueValues"]], + "aggregate": [ + { "type": "sum", "names": ["entries"] } + ] + }, + "sort": { + "top": 10, + "by": [["entries_sum", "desc"]] + } + }, + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "name_uniqueValues", + "yAxisData": ["entries_sum"], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "Records", + "showXAxisLabel": false, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": true, + "showValue": false, + "grouping": false, + "range": { "type": "auto" }, + "color": { "type": "default" } + } + } + } + } + }, + { + "i": "86409b9b-56a0-445b-b9d4-072c08567094", + "x": 2, + "y": 3, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Top 10 Searches", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[topSearches]}}", + "name": "topSearches", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "search", + "operation": "notempty" + } + ] + }, + "group": { + "by": [["search", "uniqueValues"]], + "aggregate": [{ "type": "sum", "names": ["count"] }] + }, + "sort": { + "top": 10, + "by": [["count_sum", "desc"]] + } + }, + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).or(__.has(\"sourceType\", \"Index\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "search_uniqueValues", + "count_sum" + ], + "hiddenColumns": [] + } + } + } + } + }, + { + "i": "d8077738-aec5-49f0-bd1b-cf7cb8823ebe", + "x": 0, + "y": 5, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Search Volume", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[searchCount]}}", + "name": "searchCount", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "count", + "operation": "greaterthan", + "value": "0" + } + ] + } + }, + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).or(__.has(\"sourceType\", \"Index\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "date", + "yAxisColumn": ["count"], + "seriesColumn": "index", + "showLegend": true, + "legendPosition": "bottom", + "dataPoints": false, + "showTrendLine": false, + "cumulative": false + } + } + } + } + }, + { + "i": "55b48169-658b-44ca-85a3-3329fc59aa56", + "x": 2, + "y": 6, + "w": 2, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "All Indices", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[indexes]}}", + "name": "indexes", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [["entries", "desc"]] + } + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "entries", + "dataSize", + "fileSize", + "lastBuildTimeS", + "numberOfPendingTasks" + ], + "hiddenColumns": [ + "pendingTask", + "updatedAt", + "primary" + ] + } + } + } + } + } + ] + } +} diff --git a/plugins/Algolia/v1/defaultContent/scopes.json b/plugins/Algolia/v1/defaultContent/scopes.json new file mode 100644 index 00000000..3a3c95be --- /dev/null +++ b/plugins/Algolia/v1/defaultContent/scopes.json @@ -0,0 +1,14 @@ +[ + { + "name": "Algolia Indices", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Index"] } + }, + "variable": { + "name": "Algolia Index", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] diff --git a/plugins/Algolia/v1/docs/README.md b/plugins/Algolia/v1/docs/README.md new file mode 100644 index 00000000..1d2dc488 --- /dev/null +++ b/plugins/Algolia/v1/docs/README.md @@ -0,0 +1,50 @@ +## What this plugin monitors + +Monitor your [Algolia](https://www.algolia.com/) search application from SquaredUp. This plugin imports your Algolia **indices** as objects and provides dashboards covering index size and growth, search volume, and search quality (no-result rate, top searches). + +- **Indices** — every index in your Algolia application is imported as an object, with its record count, data/file size, last build time and pending-task count. +- **Search analytics** (per index) — search volume over time, the no-results rate, your most popular searches, and the searches that returned no results. + +Out of the box you get an **Overview** dashboard (totals and top indices across the whole application) and an **Index** perspective (per-index size, search volume, and search-quality tiles). + +## Prerequisites — getting your credentials + +You need two values from the Algolia dashboard: your **Application ID** and an **API key**. + +1. Sign in to the [Algolia dashboard](https://dashboard.algolia.com/). +2. Go to **Settings → API Keys** (or **Account → API Keys**). +3. Copy your **Application ID** (shown at the top of the API Keys page — a short string like `LIxxxxxxxx`). +4. For the API key, use your **Admin API Key**, **or** (recommended) create a dedicated key with only the permissions this plugin needs. Click **All API keys → New API Key** and grant these ACLs: + - `listIndexes` — to import your indices + - `settings` — to read index configuration + - `analytics` — to read Search Analytics + - Leave **Indices** set to _all_ (or restrict to the indices you want to monitor). + + A read-only **Search-Only API Key** is **not** sufficient — it lacks `listIndexes`, `settings` and `analytics`. + +> **Tip:** Using a scoped custom key rather than your Admin key follows least-privilege best practice and means the key can be rotated without affecting other integrations. + +## Configuration fields + +| Field | Required | What it is / where to find it | +| -------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Application ID** | Yes | Your Algolia Application ID, from **Settings → API Keys** in the Algolia dashboard. Also forms your API host (`https://.algolia.net`). | +| **API key** | Yes | An Admin key, or a custom key with the `listIndexes`, `settings` and `analytics` ACLs (see above). Stored encrypted. | +| **Analytics region** | No (default **US**) | The region your Algolia app's analytics data is stored in. Choose **EU** only if your application was created in Algolia's EU (Germany) region — otherwise analytics tiles will be empty. Defaults to US. | + +## What gets indexed + +| Object type | Represents | Key properties | +| ----------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Index** | A single search index in your application | Records (entries), data size, file size, last build time (s), number of pending tasks, pending-task flag, last updated, primary index (for replicas) | + +Each index's object ID is its index name, which is unique within an application. + +## Known limitations + +- **Search Analytics only.** This version covers the Search and Search Analytics APIs. It does **not** include the **Usage API** (operations/records time series) or the **Monitoring API** (latency, indexing time, infrastructure metrics) — those require an Algolia **Premium/Enterprise** plan and dedicated Usage/Monitoring API keys. +- **Analytics granularity is daily.** Algolia's Search Analytics aggregates per calendar day, so the analytics tiles use timeframes of **7 days and longer**. Shorter timeframes (e.g. last hour) are not meaningful for this data. +- **Analytics need search traffic.** Indices with no recent search activity will show empty analytics tiles — this is expected, not an error. +- **Analytics region matters.** If your app is in Algolia's EU region but the plugin is left on the US analytics region (or vice-versa), analytics calls succeed but return no data. Set the **Analytics region** field to match your app. +- **Rate limits.** The Analytics API is limited to ~100 requests/minute per application; very large numbers of indices on a single dashboard may be throttled. +- **`listIndexes` required.** If you authenticate with a Search-Only key, setup validation will fail because the key cannot list indices. diff --git a/plugins/Algolia/v1/icon.svg b/plugins/Algolia/v1/icon.svg new file mode 100644 index 00000000..ab64fef5 --- /dev/null +++ b/plugins/Algolia/v1/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/Algolia/v1/indexDefinitions/default.json b/plugins/Algolia/v1/indexDefinitions/default.json new file mode 100644 index 00000000..241577f1 --- /dev/null +++ b/plugins/Algolia/v1/indexDefinitions/default.json @@ -0,0 +1,24 @@ +{ + "steps": [ + { + "name": "indexes", + "dataStream": { "name": "indexes" }, + "timeframe": "none", + "objectMapping": { + "id": "name", + "name": "name", + "type": { "value": "Index" }, + "properties": [ + "entries", + "dataSize", + "fileSize", + "lastBuildTimeS", + "numberOfPendingTasks", + "pendingTask", + "updatedAt", + "primary" + ] + } + } + ] +} diff --git a/plugins/Algolia/v1/metadata.json b/plugins/Algolia/v1/metadata.json new file mode 100644 index 00000000..5d4eb8d0 --- /dev/null +++ b/plugins/Algolia/v1/metadata.json @@ -0,0 +1,42 @@ +{ + "name": "algolia", + "displayName": "Algolia", + "version": "1.0.0", + "author": { "name": "@andrewmumblebee", "type": "community" }, + "description": "Monitor your Algolia search application — import indices and track index size, search volume, and search quality (no-result rate, top searches).", + "category": "Monitoring", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": false, + "restrictedToPlatforms": [], + "keywords": ["algolia", "search", "analytics", "index", "search analytics"], + "objectTypes": ["Index"], + "links": [ + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/Algolia/v1/docs/README.md", + "label": "Help adding this plugin" + }, + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/Algolia/v1", + "label": "Repository" + } + ], + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "baseUrl": "https://analytics.{{analyticsRegion}}.algolia.com", + "authMode": "none", + "headers": [ + { + "key": "X-Algolia-Application-Id", + "value": "{{applicationId}}" + }, + { "key": "X-Algolia-API-Key", "value": "{{apiKey}}" } + ], + "queryArgs": [] + } + } +} diff --git a/plugins/Algolia/v1/ui.json b/plugins/Algolia/v1/ui.json new file mode 100644 index 00000000..e5800c2f --- /dev/null +++ b/plugins/Algolia/v1/ui.json @@ -0,0 +1,28 @@ +[ + { + "type": "text", + "name": "applicationId", + "label": "Application ID", + "placeholder": "e.g. LIXXXXXXXX", + "help": "Your Algolia Application ID, found under **Settings → API Keys** in the Algolia dashboard. This also forms your API host (`https://.algolia.net`).", + "validation": { "required": true } + }, + { + "type": "password", + "name": "apiKey", + "label": "API key", + "help": "An Algolia **Admin API Key**, or a custom key with the `listIndexes`, `settings` and `analytics` ACLs. A Search-Only key will not work. Found under **Settings → API Keys**.", + "validation": { "required": true } + }, + { + "type": "switch", + "name": "analyticsRegion", + "label": "Analytics region", + "defaultValue": "us", + "options": [ + { "value": "us", "label": "US" }, + { "value": "de", "label": "EU" } + ], + "help": "The region your Algolia application's analytics data is stored in. Choose **EU** only if your application was created in Algolia's EU (Germany) region; otherwise leave as **US**. The wrong region returns empty analytics." + } +]