From 29ebc43a0b3b0246225e32525cc1be422cddc9a5 Mon Sep 17 00:00:00 2001 From: Phil Berryman Date: Tue, 22 Nov 2022 15:29:56 -0800 Subject: [PATCH] feat: add number of searches Signed-off-by: Phil Berryman --- .changeset/clean-seals-whisper.md | 5 +++++ .changeset/dry-melons-listen.md | 8 ++++++++ .changeset/silver-rats-rest.md | 5 +++++ docs/plugins/analytics.md | 14 +++++++------- .../src/engines/ElasticSearchSearchEngine.ts | 1 + .../src/PgSearchEngine/PgSearchEngine.test.ts | 3 +++ .../src/PgSearchEngine/PgSearchEngine.ts | 3 ++- .../src/engines/LunrSearchEngine.ts | 1 + .../src/service/AuthorizedSearchEngine.ts | 1 + plugins/search-common/api-report.md | 2 ++ plugins/search-common/src/types.ts | 1 + .../src/components/SearchTracker/SearchTracker.tsx | 10 +++++++--- 12 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 .changeset/clean-seals-whisper.md create mode 100644 .changeset/dry-melons-listen.md create mode 100644 .changeset/silver-rats-rest.md diff --git a/.changeset/clean-seals-whisper.md b/.changeset/clean-seals-whisper.md new file mode 100644 index 0000000000..8891d75f8f --- /dev/null +++ b/.changeset/clean-seals-whisper.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-react': minor +--- + +The `value` of a search analytics event is now set as the total number of search results (when available) diff --git a/.changeset/dry-melons-listen.md b/.changeset/dry-melons-listen.md new file mode 100644 index 0000000000..1e09619060 --- /dev/null +++ b/.changeset/dry-melons-listen.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-search-backend': minor +'@backstage/plugin-search-backend-module-elasticsearch': minor +'@backstage/plugin-search-backend-node': minor +'@backstage/plugin-search-backend-module-pg': minor +--- + +numberOfResults is now provided alongside the query result diff --git a/.changeset/silver-rats-rest.md b/.changeset/silver-rats-rest.md new file mode 100644 index 0000000000..52bb9acd6f --- /dev/null +++ b/.changeset/silver-rats-rest.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-common': minor +--- + +numberOfResults (total number of results for a given query) can now be provided by each search engine and consumed as part of the search results response diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md index 46e1d354df..e04cb9c5c6 100644 --- a/docs/plugins/analytics.md +++ b/docs/plugins/analytics.md @@ -52,13 +52,13 @@ learn how to contribute the integration yourself! The following table summarizes events that, depending on the plugins you have installed, may be captured. -| Action | Subject | Other Notes | -| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | -| `navigate` | The URL of the page that was navigated to | | -| `click` | The text of the link that was clicked on | The `to` attribute represents the URL clicked to | -| `create` | The `name` of the software being created; if no `name` property is requested by the given Software Template, then the string `new {templateName}` is used instead. | The context holds an `entityRef`, set to the template's ref (e.g. `template:default/template-name`) | -| `search` | The search term entered in any search bar component | The context holds `searchTypes`, representing `types` constraining the search | -| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided | +| Action | Subject | Other Notes | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `navigate` | The URL of the page that was navigated to | | +| `click` | The text of the link that was clicked on | The `to` attribute represents the URL clicked to | +| `create` | The `name` of the software being created; if no `name` property is requested by the given Software Template, then the string `new {templateName}` is used instead. | The context holds an `entityRef`, set to the template's ref (e.g. `template:default/template-name`) | +| `search` | The search term entered in any search bar component | - The context holds `searchTypes`, representing `types` constraining the search. The `value` represents the total number of search results for the query. This may not be visible if the permission framework is being used. | +| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided | If there is an event you'd like to see captured, please [open an issue][add-event] describing the event you want to see and the questions it diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts index a33d8939eb..72bab5b3b6 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts @@ -335,6 +335,7 @@ export class ElasticSearchSearchEngine implements SearchEngine { ), nextPageCursor, previousPageCursor, + numberOfResults: result.body.hits.total.value, }; } catch (error) { if (error.meta?.body?.error?.type === 'index_not_found_exception') { diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts index 23f8983074..c1d73c618d 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts @@ -231,6 +231,7 @@ describe('PgSearchEngine', () => { }, ], nextPageCursor: undefined, + numberOfResults: 1, }); expect(database.transaction).toHaveBeenCalledTimes(1); expect(database.query).toHaveBeenCalledWith(tx, { @@ -288,6 +289,7 @@ describe('PgSearchEngine', () => { }, })), nextPageCursor: 'MQ==', + numberOfResults: 30, }); expect(database.transaction).toHaveBeenCalledTimes(1); expect(database.query).toHaveBeenCalledWith(tx, { @@ -348,6 +350,7 @@ describe('PgSearchEngine', () => { })) .slice(25), previousPageCursor: 'MA==', + numberOfResults: 5, }); expect(database.transaction).toHaveBeenCalledTimes(1); expect(database.query).toHaveBeenCalledWith(tx, { diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts index f53c433c2f..b0e8d0c6d2 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts @@ -194,6 +194,7 @@ export class PgSearchEngine implements SearchEngine { const previousPageCursor = hasPreviousPage ? encodePageCursor({ page: page - 1 }) : undefined; + const numberOfResults = rows.length; const results = pageRows.map( ({ type, document, highlight }, index): IndexableResult => ({ @@ -215,7 +216,7 @@ export class PgSearchEngine implements SearchEngine { }), ); - return { results, nextPageCursor, previousPageCursor }; + return { results, nextPageCursor, previousPageCursor, numberOfResults }; } } diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts index d60f9951f4..ff2c828402 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts @@ -238,6 +238,7 @@ export class LunrSearchEngine implements SearchEngine { }), }, })), + numberOfResults: results.length, nextPageCursor, previousPageCursor, }; diff --git a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts index ade43280be..52c87d76c7 100644 --- a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts +++ b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts @@ -205,6 +205,7 @@ export class AuthorizedSearchEngine implements SearchEngine { (nextPageCursor || filteredResults.length > targetResults) ? encodePageCursor({ page: page + 1 }) : undefined, + numberOfResults: undefined, }; } diff --git a/plugins/search-common/api-report.md b/plugins/search-common/api-report.md index 61e5d22257..2006689ab9 100644 --- a/plugins/search-common/api-report.md +++ b/plugins/search-common/api-report.md @@ -73,6 +73,8 @@ export interface ResultSet { // (undocumented) nextPageCursor?: string; // (undocumented) + numberOfResults?: number; + // (undocumented) previousPageCursor?: string; // (undocumented) results: Result[]; diff --git a/plugins/search-common/src/types.ts b/plugins/search-common/src/types.ts index 54abc9e1b1..2ea4a490ca 100644 --- a/plugins/search-common/src/types.ts +++ b/plugins/search-common/src/types.ts @@ -87,6 +87,7 @@ export interface ResultSet { results: Result[]; nextPageCursor?: string; previousPageCursor?: string; + numberOfResults?: number; } /** diff --git a/plugins/search-react/src/components/SearchTracker/SearchTracker.tsx b/plugins/search-react/src/components/SearchTracker/SearchTracker.tsx index f900e45d45..3b758a0c47 100644 --- a/plugins/search-react/src/components/SearchTracker/SearchTracker.tsx +++ b/plugins/search-react/src/components/SearchTracker/SearchTracker.tsx @@ -23,14 +23,18 @@ import { useSearch } from '../../context'; */ export const TrackSearch = ({ children }: { children: React.ReactChild }) => { const analytics = useAnalytics(); - const { term } = useSearch(); + const { term, result } = useSearch(); + + const numberOfResults = result.value?.numberOfResults ?? undefined; useEffect(() => { if (term) { // Capture analytics search event with search term provided as value - analytics.captureEvent('search', term); + analytics.captureEvent('search', term, { + value: numberOfResults, + }); } - }, [analytics, term]); + }, [analytics, term, numberOfResults]); return <>{children}; };