Vue Storefront is now Alokai! Learn More
Facet normalizer

Facet normalizer

Concept of facets exists in both Unified Data Layer world and BigCommerce. The normalizeFacet function maps BigCommerce SearchProductFilter options into Unified SfFacet.

Parameters

NameTypeDefault valueDescription
contextNormalizeFacetContextContext which contains getFacetType
filterGraphQL.SearchProductFilterBigCommerce SearchProductFilter

Extending

The SfFacet is returned from SearchProducts Method. If the SfFacet structure doesn't contain the information you need for your Storefront, you can extend its logic using the addCustomFields API.

Source

In BigCommerce, SearchProductFilter has a dynamic type. It can be one of the following:

  • BrandSearchFilter
  • RatingSearchFilter
  • CategorySearchFilter
  • ProductAttributeSearchFilter
facet.ts
import type { GraphQL } from "@vsf-enterprise/bigcommerce-api";
import type { SfFacetType } from "@vue-storefront/unified-data-model";
import { SfFacetTypes, type SfFacet } from "@vue-storefront/unified-data-model";
import { graphqlTypeGuardFactory } from "@shared/utils";
import { defineNormalizer } from "../defineNormalizer";

export const isBrandSearchFilter =
  graphqlTypeGuardFactory<GraphQL.BrandSearchFilter>("BrandSearchFilter");
export const isRatingSearchFilter =
  graphqlTypeGuardFactory<GraphQL.RatingSearchFilter>("RatingSearchFilter");
export const isCategorySearchFilter =
  graphqlTypeGuardFactory<GraphQL.CategorySearchFilter>("CategorySearchFilter");
export const isProductAttributeSearchFilter =
  graphqlTypeGuardFactory<GraphQL.ProductAttributeSearchFilter>("ProductAttributeSearchFilter");

// Method is included in `docs/limitations/bigcommerce.md`. If you change it, please update the docs accordingly.
export const normalizeFacet = defineNormalizer.normalizeFacet((context, filter) => {
  const metadata = {
    label: filter.name,
    name: filter.name,
  };
  const values = normalizeFacetValues(filter);
  const { getFacetType = defaultGetFacetType } = context;

  if (values && values.length > 0) {
    return { ...metadata, values, type: getFacetType(filter) };
  }

  return null;
});

// Check here for all available types https://github.com/vuestorefront/bigcommerce/blob/main/packages/api-client/src/api/filters/get/FiltersQuery.ts
function normalizeFacetValues(filter: GraphQL.SearchProductFilter): SfFacet["values"] | undefined {
  let values: SfFacet["values"] | undefined;

  if (isBrandSearchFilter(filter)) {
    values = filter.brands.edges?.filter(Boolean).map((edge) => ({
      label: edge.node.name,
      value: edge.node.entityId.toString(),
      productCount: edge.node.productCount,
    }));
  } else if (isRatingSearchFilter(filter)) {
    values = filter.ratings.edges?.filter(Boolean).map((edge) => ({
      label: edge.node.value,
      value: edge.node.value,
      productCount: edge.node.productCount,
    }));
  } else if (isCategorySearchFilter(filter)) {
    values = filter.categories.edges
      ?.filter(Boolean)
      .filter((edge) => Boolean(edge.node.name))
      .map((edge) => ({
        label: edge.node.name,
        value: edge.node.entityId.toString(),
        productCount: edge.node.productCount,
      }));
  } else if (isProductAttributeSearchFilter(filter)) {
    values = filter.attributes.edges?.filter(Boolean).map((edge) => ({
      label: edge.node.value,
      value: edge.node.value,
      productCount: edge.node.productCount,
    }));
  }

  return values;
}

function defaultGetFacetType(facet: GraphQL.SearchProductFilter): SfFacetType {
  return SfFacetTypes.MULTI_SELECT;
}