{props.icon && {props.icon}}
diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts
index d11d8a1f90..31112e3446 100644
--- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts
+++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts
@@ -334,27 +334,30 @@ export class ElasticSearchSearchEngine implements SearchEngine {
: undefined;
return {
- results: result.body.hits.hits.map((d: ElasticSearchResult) => {
- const resultItem: IndexableResult = {
- type: this.getTypeFromIndex(d._index),
- document: d._source,
- };
-
- if (d.highlight) {
- resultItem.highlight = {
- preTag: this.highlightOptions.preTag as string,
- postTag: this.highlightOptions.postTag as string,
- fields: Object.fromEntries(
- Object.entries(d.highlight).map(([field, fragments]) => [
- field,
- fragments.join(this.highlightOptions.fragmentDelimiter),
- ]),
- ),
+ results: result.body.hits.hits.map(
+ (d: ElasticSearchResult, index: number) => {
+ const resultItem: IndexableResult = {
+ type: this.getTypeFromIndex(d._index),
+ document: d._source,
+ rank: pageSize * page + index + 1,
};
- }
- return resultItem;
- }),
+ if (d.highlight) {
+ resultItem.highlight = {
+ preTag: this.highlightOptions.preTag as string,
+ postTag: this.highlightOptions.postTag as string,
+ fields: Object.fromEntries(
+ Object.entries(d.highlight).map(([field, fragments]) => [
+ field,
+ fragments.join(this.highlightOptions.fragmentDelimiter),
+ ]),
+ ),
+ };
+ }
+
+ return resultItem;
+ },
+ ),
nextPageCursor,
previousPageCursor,
};
diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts
index 445d1bb5c1..bf98c58de9 100644
--- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts
+++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts
@@ -18,6 +18,7 @@ import { SearchEngine } from '@backstage/plugin-search-backend-node';
import {
SearchQuery,
IndexableResultSet,
+ IndexableResult,
} from '@backstage/plugin-search-common';
import { PgSearchEngineIndexer } from './PgSearchEngineIndexer';
import {
@@ -104,10 +105,13 @@ export class PgSearchEngine implements SearchEngine {
? encodePageCursor({ page: page - 1 })
: undefined;
- const results = pageRows.map(({ type, document }) => ({
- type,
- document,
- }));
+ const results = pageRows.map(
+ ({ type, document }, index): IndexableResult => ({
+ type,
+ document,
+ rank: page * pageSize + index + 1,
+ }),
+ );
return { results, nextPageCursor, previousPageCursor };
}
diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts
index 4a5f4cdd18..03cb421653 100644
--- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts
+++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts
@@ -207,9 +207,10 @@ export class LunrSearchEngine implements SearchEngine {
// Translate results into IndexableResultSet
const realResultSet: IndexableResultSet = {
- results: results.slice(offset, offset + pageSize).map(d => ({
+ results: results.slice(offset, offset + pageSize).map((d, index) => ({
type: d.type,
document: this.docStore[d.result.ref],
+ rank: page * pageSize + index + 1,
highlight: {
preTag: this.highlightPreTag,
postTag: this.highlightPostTag,
diff --git a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts
index a74cdf8eef..ade43280be 100644
--- a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts
+++ b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts
@@ -189,10 +189,15 @@ export class AuthorizedSearchEngine implements SearchEngine {
);
return {
- results: filteredResults.slice(
- page * this.pageSize,
- (page + 1) * this.pageSize,
- ),
+ results: filteredResults
+ .slice(page * this.pageSize, (page + 1) * this.pageSize)
+ .map((result, index) => {
+ // Overwrite any/all rank entries to avoid leaking knowledge of filtered results.
+ return {
+ ...result,
+ rank: page * this.pageSize + index + 1,
+ };
+ }),
previousPageCursor:
page === 0 ? undefined : encodePageCursor({ page: page - 1 }),
nextPageCursor:
diff --git a/plugins/search-react/api-report.md b/plugins/search-react/api-report.md
index f9bb993fd6..e4afee45ca 100644
--- a/plugins/search-react/api-report.md
+++ b/plugins/search-react/api-report.md
@@ -38,6 +38,7 @@ export type DefaultResultListItemProps = {
secondaryAction?: ReactNode;
result: SearchDocument;
highlight?: ResultHighlight;
+ rank?: number;
lineClamp?: number;
};
diff --git a/plugins/search-react/src/components/DefaultResultListItem/DefaultResultListItem.tsx b/plugins/search-react/src/components/DefaultResultListItem/DefaultResultListItem.tsx
index ff6f3d7fe1..f4260525d6 100644
--- a/plugins/search-react/src/components/DefaultResultListItem/DefaultResultListItem.tsx
+++ b/plugins/search-react/src/components/DefaultResultListItem/DefaultResultListItem.tsx
@@ -15,7 +15,7 @@
*/
import React, { ReactNode } from 'react';
-import { AnalyticsContext } from '@backstage/core-plugin-api';
+import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api';
import {
ResultHighlight,
SearchDocument,
@@ -40,6 +40,7 @@ export type DefaultResultListItemProps = {
secondaryAction?: ReactNode;
result: SearchDocument;
highlight?: ResultHighlight;
+ rank?: number;
lineClamp?: number;
};
@@ -51,12 +52,21 @@ export type DefaultResultListItemProps = {
export const DefaultResultListItemComponent = ({
result,
highlight,
+ rank,
icon,
secondaryAction,
lineClamp = 5,
}: DefaultResultListItemProps) => {
+ const analytics = useAnalytics();
+ const handleClick = () => {
+ analytics.captureEvent('discover', result.title, {
+ attributes: { to: result.location },
+ value: rank,
+ });
+ };
+
return (
-
+
{icon && {icon}}
{
+ analytics.captureEvent('discover', result.title, {
+ attributes: { to: result.location },
+ value: rank,
+ });
+ };
+
const TextItem = () => {
const resultTitle = highlight?.fields.title ? (
) =>
- asLink ? {children} : <>{children}>;
+ asLink ? (
+
+ {children}
+
+ ) : (
+ <>{children}>
+ );
const ListItemWrapper = ({ children }: PropsWithChildren<{}>) =>
asListItem ? (