Skip to content

Commit

Permalink
feat(): add executed query length to search response (#1733)
Browse files Browse the repository at this point in the history
  • Loading branch information
jordantsanz authored Nov 22, 2024
1 parent 1b6d857 commit 1388046
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 7 deletions.
3 changes: 2 additions & 1 deletion packages/common/src/search/_internal/portalSearchGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
IHubSearchResult,
IQuery,
} from "../types";
import { getNextFunction } from "../utils";
import { getNextFunction, getKilobyteSizeOfQuery } from "../utils";
import { expandPredicate } from "./expandPredicate";

/**
Expand Down Expand Up @@ -106,6 +106,7 @@ async function searchPortal(
resp.total,
searchPortal
),
executedQuerySize: getKilobyteSizeOfQuery(searchOptions.q),
};
}

Expand Down
14 changes: 12 additions & 2 deletions packages/common/src/search/_internal/portalSearchItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ import {
IPredicate,
IQuery,
} from "../types";
import { addDefaultItemSearchPredicates, getNextFunction } from "../utils";
import {
addDefaultItemSearchPredicates,
getNextFunction,
getKilobyteSizeOfQuery,
expandPortalQuery,
} from "../utils";
import { convertPortalAggregations } from "./portalSearchUtils";
import { expandPredicate } from "./expandPredicate";
import HubError from "../../HubError";
Expand Down Expand Up @@ -59,6 +64,9 @@ export function portalSearchItemsAsItems(
}

/**
* DEPRECATED - use expandPortalQuery instead
*
*
* @internal
* Expand an IQuery by applying well-known filters and predicates,
* and then expanding all the predicates into IMatchOption objects.
Expand Down Expand Up @@ -103,7 +111,7 @@ function processSearchParams(options: IHubSearchOptions, query: IQuery) {
);
}

const updatedQuery = expandQuery(query);
const updatedQuery = expandPortalQuery(query);

// Serialize the all the groups for portal
const so = serializeQueryForPortal(updatedQuery);
Expand Down Expand Up @@ -166,6 +174,7 @@ async function searchPortalAsItem(
resp.total,
searchPortalAsItem
),
executedQuerySize: getKilobyteSizeOfQuery(searchOptions.q),
};
}

Expand Down Expand Up @@ -209,6 +218,7 @@ async function searchPortalAsHubSearchResult(
resp.total,
searchPortalAsHubSearchResult
),
executedQuerySize: getKilobyteSizeOfQuery(searchOptions.q),
};
}

Expand Down
3 changes: 3 additions & 0 deletions packages/common/src/search/_internal/portalSearchUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { getNextFunction } from "../utils";
import { expandPredicate } from "./expandPredicate";
import { cloneObject } from "../../util";
import { getKilobyteSizeOfQuery } from "../utils";

function buildSearchOptions(
query: IQuery,
Expand Down Expand Up @@ -203,6 +204,7 @@ async function searchPortal(
resp.total,
searchPortal
),
executedQuerySize: getKilobyteSizeOfQuery(searchOptions.q),
};
}

Expand Down Expand Up @@ -232,6 +234,7 @@ async function searchCommunity(
resp.total,
searchCommunity
),
executedQuerySize: getKilobyteSizeOfQuery(searchOptions.q),
};
}

Expand Down
3 changes: 2 additions & 1 deletion packages/common/src/search/explainQueryResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IFilter, IPredicate, IQuery } from "./types";
import { expandQuery } from "./_internal/portalSearchItems";
import { cloneObject } from "../util";
import { explainFilter } from "./_internal/explainFilter";
import { expandPortalQuery } from "./utils";

/**
* Explanation of why a result matched a query
Expand Down Expand Up @@ -123,7 +124,7 @@ export async function explainQueryResult(
}

// Expand the query so we have a standardized structure to work with
const expandedQuery = expandQuery(query);
const expandedQuery = expandPortalQuery(query);

// iterate the filters on the query and get explanations for each
const filterExplanations: IFilterExplanation[] = [];
Expand Down
7 changes: 7 additions & 0 deletions packages/common/src/search/types/IHubSearchResponse.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IMessage } from "../../types/IMessage";
import { IHubAggregation } from "./IHubAggregation";
import { Kilobyte } from "./types";

/**
* Defines a generic search response interface with parameterized result type
Expand Down Expand Up @@ -36,4 +37,10 @@ export interface IHubSearchResponse<T> {
* Array of messages / warnings
*/
messages?: IMessage[];

/**
* The length of the query string that was just executed in the search,
* measured in kilobytes
*/
executedQuerySize?: Kilobyte;
}
7 changes: 7 additions & 0 deletions packages/common/src/search/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,10 @@ export interface ICatalogSearchResponse {

export interface ISearchResponseHash
extends Record<string, IHubSearchResponse<IHubSearchResult>> {}

/**
* Type wrapper for a kilobyte
* This is complete syntactic sugar, it makes sizes easier to understand
* with units as a type
*/
export type Kilobyte = number;
44 changes: 43 additions & 1 deletion packages/common/src/search/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
IGroup,
ISearchGroupUsersOptions,
ISearchOptions,
SearchQueryBuilder,
} from "@esri/arcgis-rest-portal";
import { isPageType } from "../content/_internal/internalContentUtils";
import { IHubSite } from "../core";
Expand All @@ -22,14 +23,20 @@ import {
IWellKnownApis,
IApiDefinition,
NamedApis,
Kilobyte,
} from "./types/types";
import { WellKnownCollection } from "./wellKnownCatalog";
import {
isLegacySearchCategory,
LegacySearchCategory,
} from "./_internal/commonHelpers/isLegacySearchCategory";
import { toCollectionKey } from "./_internal/commonHelpers/toCollectionKey";
import { expandQuery } from "./_internal/portalSearchItems";
import {
applyWellKnownCollectionFilters,
applyWellKnownItemPredicates,
expandPredicates,
expandQuery,
} from "./_internal/portalSearchItems";

/**
* Well known APIs
Expand Down Expand Up @@ -369,3 +376,38 @@ export function addDefaultItemSearchPredicates(query: IQuery): IQuery {
queryWithDefaultItemPredicates.filters.push(defaultPredicates);
return queryWithDefaultItemPredicates;
}

/**
* Returns the size in kilobytes of a query string or a SearchQueryBuilder.
* This is used to later determine if a query is too large or almost too large to be sent to the server.
* @param query
* @returns
*/
export function getKilobyteSizeOfQuery(
query: string | SearchQueryBuilder
): Kilobyte {
// convert query to string if it isn't already
const queryString = typeof query === "string" ? query : query.toParam();

// get the size of the query string using the TextEncoder api
const encoder = new TextEncoder();
const encodedString = encoder.encode(queryString);
const sizeInBytes = encodedString.length;
const sizeInKB = sizeInBytes / 1024; // Convert bytes to kilobytes
return sizeInKB;
}

/**
* Expand an item IQuery for portal by applying well-known filters and predicates,
* and then expanding all the predicates into IMatchOption objects.
* @param query `IQuery` to expand
* @returns IQuery
*/
export function expandPortalQuery(query: IQuery): IQuery {
let updatedQuery = applyWellKnownCollectionFilters(query);
// Expand well-known filterGroups
// TODO: Should we remove this with the whole idea of collections?
updatedQuery = applyWellKnownItemPredicates(updatedQuery);
// Expand the individual predicates in each filter
return expandPredicates(updatedQuery);
}
84 changes: 82 additions & 2 deletions packages/common/test/search/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { IGroup, ISearchOptions, IUser } from "@esri/arcgis-rest-portal";
import {
IGroup,
ISearchOptions,
IUser,
SearchQueryBuilder,
} from "@esri/arcgis-rest-portal";
import { IHubSite, IQuery, ISearchResponse } from "../../src";
import { IHubSearchResult, IRelativeDate } from "../../src/search";
import {
IHubSearchResult,
IRelativeDate,
serializeQueryForPortal,
} from "../../src/search";
import {
expandApis,
getUserThumbnailUrl,
Expand All @@ -11,6 +20,7 @@ import {
migrateToCollectionKey,
getResultSiteRelativeLink,
getGroupPredicate,
getKilobyteSizeOfQuery,
} from "../../src/search/utils";
import { MOCK_AUTH } from "../mocks/mock-auth";
import { mockUserSession } from "../test-helpers/fake-user-session";
Expand Down Expand Up @@ -476,4 +486,74 @@ describe("Search Utils:", () => {
});
});
});

describe("getKilobyteSizeOfQuery", () => {
// These tests create a blob
it("returns the size of the query in kilobytes", () => {
const query = {
targetEntity: "item",
filters: [
{
predicates: [
{
type: "Web Map",
typekeywords: { any: ["my|web|map"] },
},
],
},
],
} as IQuery;
const stringQuery = serializeQueryForPortal(query).q;
const size = 0.044921875;
const chk = getKilobyteSizeOfQuery(stringQuery);
expect(chk).toEqual(size);
});

it("returns 0 if the query is empty", () => {
const queryString = "";
const size = 0;
const chk = getKilobyteSizeOfQuery(queryString);
expect(chk).toEqual(size);
});

it("handles special characters in the query", () => {
const query = {
targetEntity: "item",
filters: [
{
predicates: [
{
type: "Web Map",
title: "🚀🚀🚀",
},
],
},
],
} as IQuery;
const stringQuery = serializeQueryForPortal(query).q;
const size = 0.0400390625;
const chk = getKilobyteSizeOfQuery(stringQuery);
expect(chk).toEqual(size);
});

it("handles a SearchQueryBuilder object", () => {
const query = new SearchQueryBuilder()
.match("Patrick")
.in("owner")
.and()
.startGroup()
.match("Web Mapping Application")
.in("type")
.or()
.match("Mobile Application")
.in("type")
.or()
.match("Application")
.in("type")
.endGroup();
const size = 0.0966796875;
const chk = getKilobyteSizeOfQuery(query);
expect(chk).toEqual(size);
});
});
});

0 comments on commit 1388046

Please sign in to comment.