Skip to content
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

feat: composition #6437

Merged
merged 29 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f1722d9
first tentative implementation
e-krebs Nov 14, 2024
9586345
example
e-krebs Nov 14, 2024
5b1941f
fix: don't remove this
e-krebs Nov 18, 2024
a02cfff
chore: better example + a TODO
e-krebs Nov 18, 2024
b903d56
fix: wording
e-krebs Nov 18, 2024
cad571e
clean: revert temporary examples change
e-krebs Nov 19, 2024
2bf8b2f
fix: better syntax to avoid destructuring
e-krebs Nov 19, 2024
876aae0
fix: make CI pass
e-krebs Nov 19, 2024
272d10b
fix: update bundle sizes
e-krebs Nov 19, 2024
5ee422a
fix: round up bundle sizes
e-krebs Nov 19, 2024
2667e0b
chore: use SearchParameters to handle composition ID
e-krebs Nov 20, 2024
566621c
fix: rollback to initial naming
e-krebs Nov 20, 2024
71374c2
fix: apply suggestions from code review
e-krebs Nov 20, 2024
8d6397f
chore: use index internally (vs. compositionID) so that sortBy works …
e-krebs Nov 20, 2024
3156481
chore: better comment
e-krebs Nov 20, 2024
9d9cbd5
Update packages/algoliasearch-helper/index.d.ts
e-krebs Nov 20, 2024
7fcd98b
chore: no need to delete facets & maxValuesPerFacet anymore 🎉
e-krebs Nov 22, 2024
38bf887
chore: don't allow index widget when in composition mode (#6459)
e-krebs Dec 4, 2024
f982c9f
feat: make composition compatible with vue (#6458)
e-krebs Dec 4, 2024
8e1d0bb
feat: handle disjunctive facets with composition API (#6485)
e-krebs Dec 20, 2024
e6c227d
feat: add searchForFacetValues for composition (#6489)
e-krebs Dec 21, 2024
0db51c5
fix: update year in snapshot
e-krebs Jan 2, 2025
d1818fb
fix: query & searchQuery where mixed when using searchForCompositionF…
e-krebs Jan 2, 2025
a22d8d1
feat: make composition work server-side + some typing (#6509)
e-krebs Jan 6, 2025
ad7535e
tests: Composition (#6510)
e-krebs Jan 7, 2025
f2a64b1
fix: composition search type (#6515)
e-krebs Jan 8, 2025
7622622
Merge branch 'master' into feat/composition
e-krebs Jan 8, 2025
70a295d
fix: merge error
e-krebs Jan 8, 2025
a7b5019
review feedback
e-krebs Jan 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"files": [
{
"path": "packages/algoliasearch-helper/dist/algoliasearch.helper.js",
"maxSize": "41.25 kB"
"maxSize": "41.75 kB"
},
{
"path": "packages/algoliasearch-helper/dist/algoliasearch.helper.min.js",
"maxSize": "13.25 kB"
"maxSize": "13.50 kB"
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.production.min.js",
Expand All @@ -18,23 +18,23 @@
},
{
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
"maxSize": "51.25 kB"
"maxSize": "51.75 kB"
},
{
"path": "packages/react-instantsearch/dist/umd/ReactInstantSearch.min.js",
"maxSize": "65.50 kB"
},
{
"path": "packages/vue-instantsearch/vue2/umd/index.js",
"maxSize": "68.75 kB"
"maxSize": "69.25 kB"
},
{
"path": "packages/vue-instantsearch/vue3/umd/index.js",
"maxSize": "69 kB"
"maxSize": "69.50 kB"
},
{
"path": "packages/vue-instantsearch/vue2/cjs/index.js",
"maxSize": "20.25 kB"
"maxSize": "20.50 kB"
},
{
"path": "packages/vue-instantsearch/vue3/cjs/index.js",
Expand Down
32 changes: 29 additions & 3 deletions packages/algoliasearch-helper/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
TrendingFacetsQuery,
TrendingItemsQuery,
PlainRecommendParameters as ClientPlainRecommendParameters,
CompositionClient,
} from './types/algoliasearch';

/**
Expand All @@ -30,7 +31,7 @@ import type {
* @param searchResultsOptions
*/
declare function algoliasearchHelper(
client: SearchClient,
client: SearchClient | CompositionClient,
index: string,
opts?: algoliasearchHelper.PlainSearchParameters,
searchResultsOptions?: algoliasearchHelper.SearchResultsOptions
Expand Down Expand Up @@ -98,6 +99,11 @@ declare namespace algoliasearchHelper {
*/
searchOnlyWithDerivedHelpers(): this;

/**
* Private method to search using composition API
*/
searchWithComposition(): this;

/**
* Private method for search, without triggering events
*/
Expand Down Expand Up @@ -192,6 +198,26 @@ declare namespace algoliasearchHelper {
userState?: PlainSearchParameters
): Promise<SearchForFacetValues.Result>;

/**
* Search for facet values using the Composition API & based on a query and the name of a faceted attribute.
* This triggers a search and will return a promise. On top of using the query, it also sends
* the parameters from the state so that the search is narrowed down to only the possible values.
*
* See the description of [FacetSearchResult](reference.html#FacetSearchResult)
* @param facet the name of the faceted attribute
* @param query the string query for the search
* @param [maxFacetHits] the maximum number values returned. Should be > 0 and <= 100
* @param [userState] the set of custom parameters to use on top of the current state. Setting a property to `undefined` removes
* it in the generated query.
* @return the results of the search
*/
searchForCompositionFacetValues(
facet: string,
query: string,
maxFacetHits: number,
userState?: PlainSearchParameters
): Promise<SearchForFacetValues.Result>;

/**
* Sets the text query used for the search.
*
Expand Down Expand Up @@ -379,8 +405,8 @@ declare namespace algoliasearchHelper {
*/
containsRefinement(...any: any[]): any;
clearCache(): this;
setClient(client: SearchClient): this;
getClient(): SearchClient;
setClient(client: SearchClient | CompositionClient): this;
getClient(): SearchClient | CompositionClient;
derive(
deriveFn: (oldParams: SearchParameters) => SearchParameters,
deriveRecommendFn?: (
Expand Down
144 changes: 144 additions & 0 deletions packages/algoliasearch-helper/src/algoliasearch.helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ AlgoliaSearchHelper.prototype.searchOnlyWithDerivedHelpers = function () {
return this;
};

AlgoliaSearchHelper.prototype.searchWithComposition = function () {
this._runComposition({ onlyWithDerivedHelpers: true });
return this;
};
/**
* Sends the recommendation queries set in the state. When the method is
* called, it triggers a `fetch` event. The results will be available through
Expand Down Expand Up @@ -457,6 +461,81 @@ AlgoliaSearchHelper.prototype.searchForFacetValues = function (
);
};

/**
* Search for facet values using the Composition API & based on a query and the name of a faceted attribute.
* This triggers a search and will return a promise. On top of using the query, it also sends
* the parameters from the state so that the search is narrowed down to only the possible values.
*
* See the description of [FacetSearchResult](reference.html#FacetSearchResult)
* @param {string} facet the name of the faceted attribute
* @param {string} query the string query for the search
* @param {number} [maxFacetHits] the maximum number values returned. Should be > 0 and <= 100
* @param {object} [userState] the set of custom parameters to use on top of the current state. Setting a property to `undefined` removes
* it in the generated query.
* @return {promise.<FacetSearchResult>} the results of the search
*/
AlgoliaSearchHelper.prototype.searchForCompositionFacetValues = function (
facet,
query,
maxFacetHits,
userState
) {
if (typeof this.client.searchForFacetValues !== 'function') {
throw new Error(
'search for facet values (searchable) was called, but this client does not have a function client.searchForFacetValues'
);
}

var state = this.state.setQueryParameters(userState || {});
var isDisjunctive = state.isDisjunctiveFacet(facet);

this._currentNbQueries++;
// eslint-disable-next-line consistent-this
var self = this;
var searchForFacetValuesPromise;

searchForFacetValuesPromise = this.client.searchForFacetValues({
compositionID: state.index,
facetName: facet,
searchForFacetValuesRequest: {
params: {
query: query,
maxFacetHits: maxFacetHits,
searchQuery: requestBuilder._getCompositionHitsSearchParams(state),
},
},
});

this.emit('searchForFacetValues', {
state: state,
facet: facet,
query: query,
});

return searchForFacetValuesPromise.then(
function addIsRefined(content) {
self._currentNbQueries--;
if (self._currentNbQueries === 0) self.emit('searchQueueEmpty');

content = content.results[0];

content.facetHits.forEach(function (f) {
f.escapedValue = escapeFacetValue(f.value);
f.isRefined = isDisjunctive
? state.isDisjunctiveFacetRefined(facet, f.escapedValue)
: state.isFacetRefined(facet, f.escapedValue);
});

return content;
},
function (e) {
self._currentNbQueries--;
if (self._currentNbQueries === 0) self.emit('searchQueueEmpty');
throw e;
}
);
};

/**
* Sets the text query used for the search.
*
Expand Down Expand Up @@ -1578,6 +1657,71 @@ AlgoliaSearchHelper.prototype._search = function (options) {
return undefined;
};

/**
* Perform the underlying queries
* @private
* @param {boolean} [options.onlyWithDerivedHelpers=false] if true, only the derived helpers will be queried
* @return {undefined} does not return anything
* @fires search
* @fires result
* @fires error
*/
AlgoliaSearchHelper.prototype._runComposition = function () {
var state = this.state;
var states = [];
var mainQueries = [];

var derivedQueries = this.derivedHelpers.map(function (derivedHelper) {
var derivedState = derivedHelper.getModifiedState(state);
var derivedStateQueries =
requestBuilder._getCompositionQueries(derivedState);

states.push({
state: derivedState,
queriesCount: derivedStateQueries.length,
helper: derivedHelper,
});

derivedHelper.emit('search', {
state: derivedState,
results: derivedHelper.lastResults,
});

return derivedStateQueries;
});

var queries = Array.prototype.concat.apply(mainQueries, derivedQueries);

var queryId = this._queryId++;
this._currentNbQueries++;

if (!queries.length) {
return Promise.resolve({ results: [] }).then(
this._dispatchAlgoliaResponse.bind(this, states, queryId)
);
}

if (queries.length > 1) {
throw new Error('Only one query is allowed when using a composition.');
}

var query = queries[0];

try {
this.client
.search(query)
.then(this._dispatchAlgoliaResponse.bind(this, states, queryId))
.catch(this._dispatchAlgoliaError.bind(this, queryId));
} catch (error) {
// If we reach this part, we're in an internal error state
this.emit('error', {
error: error,
});
}

return undefined;
};

AlgoliaSearchHelper.prototype._recommend = function () {
var searchState = this.state;
var recommendState = this.recommendState;
Expand Down
65 changes: 65 additions & 0 deletions packages/algoliasearch-helper/src/requestBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,24 @@ var requestBuilder = {
return queries;
},

/**
* Get all the queries to send to the client, those queries can used directly
* with the Algolia client.
* @private
* @param {SearchParameters} state The state from which to get the queries
* @return {object[]} The queries
*/
_getCompositionQueries: function getQueries(state) {
dhayab marked this conversation as resolved.
Show resolved Hide resolved
return [
{
compositionID: state.index,
requestBody: {
params: requestBuilder._getCompositionHitsSearchParams(state),
},
},
];
},

/**
* Build search parameters used to fetch hits
* @private
Expand Down Expand Up @@ -159,6 +177,53 @@ var requestBuilder = {
return sortObject(merge({}, state.getQueryParams(), additionalParams));
},

/**
* Build search parameters used to fetch hits
* @private
* @param {SearchParameters} state The state from which to get the queries
* @return {object.<string, any>} The search parameters for hits
*/
_getCompositionHitsSearchParams: function (state) {
var facets = state.facets
.concat(
state.disjunctiveFacets.map(function (value) {
return 'disjunctive(' + value + ')';
})
)
.concat(requestBuilder._getHitsHierarchicalFacetsAttributes(state))
.sort();

var facetFilters = requestBuilder._getFacetFilters(state);
var numericFilters = requestBuilder._getNumericFilters(state);
var tagFilters = requestBuilder._getTagFilters(state);
var additionalParams = {};

if (facets.length > 0) {
additionalParams.facets = facets.indexOf('*') > -1 ? ['*'] : facets;
}

if (tagFilters.length > 0) {
additionalParams.tagFilters = tagFilters;
}

if (facetFilters.length > 0) {
additionalParams.facetFilters = facetFilters;
}

if (numericFilters.length > 0) {
additionalParams.numericFilters = numericFilters;
}

var params = state.getQueryParams();

delete params.highlightPreTag;
delete params.highlightPostTag;
// not a valid search parameter, it is handled in _getCompositionQueries
delete params.index;

return sortObject(merge({}, params, additionalParams));
},

/**
* Build search parameters used to fetch a disjunctive facet
* @private
Expand Down
11 changes: 11 additions & 0 deletions packages/algoliasearch-helper/types/algoliasearch.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,14 @@ export interface SearchClient {
: never;
addAlgoliaAgent?: DefaultSearchClient['addAlgoliaAgent'];
}

export interface CompositionClient {
search: <T>(request: {
compositionID: string;
requestBody: { params: SearchOptions };
}) => Promise<{
results: Array<AlgoliaSearch.SearchResponse<T>>;
}>;
initIndex?: never;
addAlgoliaAgent?: DefaultSearchClient['addAlgoliaAgent'];
}
Loading
Loading