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(core): introduce Reshape API #647

Merged
merged 10 commits into from
Aug 26, 2021
Merged

feat(core): introduce Reshape API #647

merged 10 commits into from
Aug 26, 2021

Conversation

francoischalifour
Copy link
Member

@francoischalifour francoischalifour commented Aug 6, 2021

This PR introduces the Reshape API to Autocomplete. (The reshape preset package will come later.)

Live playground

▶️ Try the Reshape API in the sandbox →

Summary

A web UI is not a one-to-one mapping with your database, and a search UI doesn't have to be a one-to-one mapping with your search engine. The Reshape API unlocks this new search representation in your UI.

The Reshape API allows to apply transformations to a group of sources:

  • Apply a limit of items (or rows) for a group of sources (limit operation)
    • Example: display 9 items total, including recent searches, query suggestions, categories and FAQ
  • Remove duplicates in a group of sources (uniqBy operation)
  • Group by attribute of a group of sources (groupBy operation)
    • Example: group items by category or by brand, as seen on DocSearch and on the Algolia Documentation website search
  • Sort sources (sort operation)
    • Example: sort sources based on static or dynamic values like the search query (currently, users need to turn their sources into plugins if they want control over the order).

This API is a transformation that happens after the sources are resolved, as a last step before rendering them. It is an enabler to apply operations like Lodash/Ramda for Autocomplete experiences.

Preview

Example of reshape with a groupBy operation on the category:

Reshape preview with a groupBy operation

Usage

import { autocomplete } from '@algolia/autocomplete-js';
import { pipe } from 'ramda';
import { groupBy, limit, uniqBy } from './functions'; // Will come from a preset package in the future

// You can pipe reshape functions
const combineSuggestions = pipe(
  uniqBy(({ source, item }) =>
    source.sourceId === 'querySuggestionsPlugin' ? item.query : item.label
  ),
  limit(4)
);
const groupByCategory = groupBy((hit) => hit.categories[0], { /* ... */ });

autocomplete({
  container: '#autocomplete',
  plugins: [recentSearchesPlugin, querySuggestionsPlugin, productsPlugin],
  reshape({ sourcesBySourceId }) {
    const {
      recentSearchesPlugin,
      querySuggestionsPlugin,
      products,
      ...rest
    } = sourcesBySourceId;

    return [
      combineSuggestions(recentSearchesPlugin, querySuggestionsPlugin),
      groupByCategory(products),
      Object.values(rest),
    ];
  },
});

Detailed design

The Reshape is a post-process step that happens after sources have been resolved in getSources(). During the getSources step, not all sources are resolved yet because some can be static (like a static list of items), and some can be async (like a search to Algolia).

During the Reshape phase, sources are static because they've been resolved. This allows simple items' manipulation.

(Combine is the old name of Reshape.)

Data flow

Rolling out

  • Release the Reshape API in the next minor. This PR enables post-transformations of sources with the new reshape option, which we can introduce in the next minor version.
  • Release Reshape functions later. This PR doesn't include the @algolia/autocomplete-preset-reshape package that we'll expose yet. I'd like to try them user-land in a wide varieties of app before exposing them as APIs.
  • Write documentation in the next days. I need to add the new reshape param to the documentation, as well as write a guide to create custom reshape functions.

Related

@codesandbox-ci
Copy link

codesandbox-ci bot commented Aug 6, 2021

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit b6db429:

Sandbox Source
@algolia/autocomplete-example-github-repositories-custom-plugin Configuration
@algolia/autocomplete-example-instantsearch Configuration
@algolia/autocomplete-example-playground Configuration
@algolia/autocomplete-example-react-renderer Configuration
@algolia/autocomplete-example-starter-algolia Configuration
@algolia/autocomplete-example-starter Configuration
@algolia/autocomplete-example-reshape Configuration
@algolia/autocomplete-example-vue Configuration
@algolia/autocomplete-example-reshape PR

Copy link
Contributor

@Haroenv Haroenv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consistent with the other ones, but why does it have a readme if it's empty?

overall, clean implementation!

import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions';
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches';
import { h, Fragment } from 'preact';
import { pipe } from 'ramda';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that it's not needed to have pipe builtin

debug: true,
openOnFocus: true,
plugins: [recentSearchesPlugin, querySuggestionsPlugin, productsPlugin],
reshape({ sourcesBySourceId }) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reasonable name!

placeholder: 'Search',
debug: true,
openOnFocus: true,
plugins: [recentSearchesPlugin, querySuggestionsPlugin, productsPlugin],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is products a plugin and not a regular source? could indicate more "regular" usage?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this gives me the idea if longer term plugins and sources maybe could be merged into a single argument? plugins could have no source, and it would allow you to order before using reshape already

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is products a plugin and not a regular source? could indicate more "regular" usage?

I wanted to focus the example on the reshape API, and not pollute the file with a huge source.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this gives me the idea if longer term plugins and sources maybe could be merged into a single argument? plugins could have no source, and it would allow you to order before using reshape already

You mean something like this?

autocomplete({
  // ...
  getSources() {
    return [
      recentSearchesPlugin,
      querySuggestionsPlugin,
      {
        // custom source
      }
    ]
  }
})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was more thinking of plugins: [somePlugin, { getSources() { customStuff }}, maybe that way you don't really need the callback form (although that limits some use cases for conditional requests, in which case your suggestion fits better)

I was just wondering if we really need to distinguish between the two

packages/autocomplete-core/src/reshape.ts Show resolved Hide resolved
packages/autocomplete-core/src/reshape.ts Show resolved Hide resolved
examples/reshape/functions/uniqBy.ts Outdated Show resolved Hide resolved
packages/autocomplete-core/src/__tests__/reshape.test.ts Outdated Show resolved Hide resolved
packages/autocomplete-core/src/__tests__/reshape.test.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@Haroenv Haroenv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks amazing!

Copy link
Member

@shortcuts shortcuts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love it! Small suggestions but nothing blocking

examples/reshape/app.tsx Outdated Show resolved Hide resolved
examples/reshape/functions/groupBy.ts Outdated Show resolved Hide resolved
Copy link
Member

@sarahdayan sarahdayan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job!

Nothing blocking, only a few remarks.

item: TItem;
}) => TItem;

export const uniqBy: AutocompleteReshapeFunction<UniqByPredicate<any>> = <
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we've pulled Ramda already in this example, I'd use it there instead of rolling our own uniqBy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really a fan of using ramda in the examples, I don't think it's so common, and might cause people to include it just because our example does so

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides, it will not make it possible for them to copy/paste.

Copy link
Member

@sarahdayan sarahdayan Aug 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@francoischalifour You recommended that I go with Ramda's groupBy in the Tags plugin example though :) Also, we do use Ramda in the example for pipe. Why do you think this is okay for this specific function?

Anyway it's no biggie and not blocking. We can discuss that later.

examples/reshape/functions/limit.ts Outdated Show resolved Hide resolved
getSource(params: { name: string; items: TItem[] }): Partial<TSource>;
};

export const groupBy: AutocompleteReshapeFunction = <
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we've pulled Ramda already in this example, I'd use it there instead of rolling our own groupBy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

examples/reshape/app.tsx Outdated Show resolved Hide resolved
packages/autocomplete-core/src/__tests__/reshape.test.ts Outdated Show resolved Hide resolved
packages/autocomplete-core/src/__tests__/reshape.test.ts Outdated Show resolved Hide resolved
@francoischalifour francoischalifour merged commit d6180d2 into next Aug 26, 2021
@francoischalifour francoischalifour deleted the feat/reshape-api branch August 26, 2021 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants