Skip to content

Commit

Permalink
feat(#549): fetch appropriate reference price and all prices for sale…
Browse files Browse the repository at this point in the history
… from GraphQL
  • Loading branch information
lukashornych committed May 31, 2024
1 parent f248708 commit 5ea0c0c
Show file tree
Hide file tree
Showing 12 changed files with 92 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.evitadb.api.CatalogContract;
import io.evitadb.core.Evita;
import io.evitadb.externalApi.graphql.api.GraphQLBuilder;
import io.evitadb.externalApi.graphql.api.tracing.OperationTracingInstrumentation;
import io.evitadb.externalApi.graphql.configuration.GraphQLConfig;
import io.evitadb.externalApi.graphql.exception.EvitaDataFetcherExceptionHandler;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ private BuiltFieldDescriptor buildEntityParentsField(@Nonnull CollectionGraphQLS
private BuiltFieldDescriptor buildEntityPriceForSaleField() {
final GraphQLFieldDefinition field = GraphQLEntityDescriptor.PRICE_FOR_SALE
.to(fieldBuilderTransformer)
// todo #538: deprecated, remove
.argument(PriceForSaleFieldHeaderDescriptor.PRICE_LIST
.to(argumentBuilderTransformer))
.argument(PriceForSaleFieldHeaderDescriptor.PRICE_LISTS
Expand Down Expand Up @@ -360,8 +361,11 @@ private BuiltFieldDescriptor buildEntityMultiplePricesForSaleAvailableField() {
private BuiltFieldDescriptor buildEntityAllPricesForSaleField() {
final GraphQLFieldDefinition field = GraphQLEntityDescriptor.ALL_PRICES_FOR_SALE
.to(fieldBuilderTransformer)
// todo #538: deprecated, remove
.argument(PriceForSaleFieldHeaderDescriptor.PRICE_LIST
.to(argumentBuilderTransformer))
.argument(PriceForSaleFieldHeaderDescriptor.PRICE_LISTS
.to(argumentBuilderTransformer))
.argument(PriceForSaleFieldHeaderDescriptor.CURRENCY
.to(argumentBuilderTransformer)
.type(typeRef(CURRENCY_ENUM.name())))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,20 @@
*/
public interface PriceForSaleFieldHeaderDescriptor {

// todo #538: deprecated, remove
PropertyDescriptor PRICE_LIST = PropertyDescriptor.builder()
.name("priceList")
.description("""
Parameter specifying desired price list of output price.
Whenever possible, use constraint `priceInPriceLists` in main query instead.
""")
.deprecate("""
Use `priceLists` argument instead.
""")
.type(nullable(String.class))
.build();
PropertyDescriptor PRICE_LISTS = PropertyDescriptor.builder()
.name("priceList")
.name("priceLists")
.description("""
Parameter specifying list of price lists ordered by priority for defining output price.
Whenever possible, use constraint `priceInPriceLists` in main query instead.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/FgForrest/evitaDB/blob/main/LICENSE
* https://github.com/FgForrest/evitaDB/blob/master/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -26,11 +26,8 @@
import graphql.execution.DataFetcherResult;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import io.evitadb.api.requestResponse.data.PriceContract;
import io.evitadb.api.requestResponse.data.PricesContract.AccompanyingPrice;
import io.evitadb.api.requestResponse.data.PricesContract.PriceForSaleWithAccompanyingPrices;
import io.evitadb.api.requestResponse.data.structure.EntityDecorator;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.dto.PrefetchedPriceForSale;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.AccompanyingPriceFieldHeaderDescriptor;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.PriceForSaleDescriptor;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.PriceForSaleFieldHeaderDescriptor;
Expand All @@ -46,11 +43,12 @@
import java.util.Optional;

/**
* TODO lho docs
* Ancestor for all data fetchers computing price for sales. The main goal is to gather all needed data for computation
* and correctly pass the price for sale down to other data fetchers.
*
* @author Lukáš Hornych, 2024
* @author Lukáš Hornych, FG Forrest a.s. (c) 2024
*/
public abstract class AbstractPriceForSaleDataFetcher<P, DTO> implements DataFetcher<DataFetcherResult<P>> {
public abstract class AbstractPriceForSaleDataFetcher<P> implements DataFetcher<DataFetcherResult<P>> {

@Nonnull
@Override
Expand All @@ -63,7 +61,7 @@ public DataFetcherResult<P> get(@Nonnull DataFetchingEnvironment environment) th
final OffsetDateTime validIn = resolveDesiredValidIn(environment, entity, context);
final AccompanyingPrice[] desiredAccompanyingPrices = resolveDesiredAccompanyingPrices(environment);

final DTO price = computePrice(entity, priceLists, currency, validIn, desiredAccompanyingPrices);
final P result = computePrices(entity, priceLists, currency, validIn, desiredAccompanyingPrices);

final Locale customLocale = environment.getArgument(PriceForSaleFieldHeaderDescriptor.LOCALE.name());
final EntityQueryContext newContext = context.toBuilder()
Expand All @@ -74,17 +72,33 @@ public DataFetcherResult<P> get(@Nonnull DataFetchingEnvironment environment) th
.desiredLocale(customLocale != null ? customLocale : context.getDesiredLocale())
.build();

return DataFetcherResult.<PriceContract>newResult()
.data(priceForSale
.map(it -> new PrefetchedPriceForSale(it.priceForSale(), entity, it.accompanyingPrices()))
.orElse(null))
return DataFetcherResult.<P>newResult()
.data(result)
.localContext(newContext)
.build();
}

/**
* Computes actual price for sale or all prices for sale. Also should prefetch accompanying prices for nested price
* fields.
*
* @param entity parent entity
* @param desiredPriceLists desired price lists
* @param desiredCurrency desired currency
* @param desiredValidIn desired validity
* @param desiredAccompanyingPrices desired accompanying prices
* @return computed price for sale
*/
@Nullable
protected abstract P computePrices(@Nonnull EntityDecorator entity,
@Nonnull String[] desiredPriceLists,
@Nonnull Currency desiredCurrency,
@Nullable OffsetDateTime desiredValidIn,
@Nonnull AccompanyingPrice[] desiredAccompanyingPrices);

@Nonnull
protected String[] resolveDesiredPricesLists(@Nonnull DataFetchingEnvironment environment,
@Nonnull EntityQueryContext context) {
@Nonnull EntityQueryContext context) {
return Optional.ofNullable((String) environment.getArgument(PriceForSaleFieldHeaderDescriptor.PRICE_LIST.name()))
.map(priceList -> new String[]{priceList})
.or(() -> Optional.ofNullable(context.getDesiredPriceInPriceLists()))
Expand All @@ -93,16 +107,16 @@ protected String[] resolveDesiredPricesLists(@Nonnull DataFetchingEnvironment en

@Nonnull
protected Currency resolveDesiredCurrency(@Nonnull DataFetchingEnvironment environment,
@Nonnull EntityQueryContext context) {
@Nonnull EntityQueryContext context) {
return Optional.ofNullable((Currency) environment.getArgument(PriceForSaleFieldHeaderDescriptor.CURRENCY.name()))
.or(() -> Optional.ofNullable(context.getDesiredPriceInCurrency()))
.orElseThrow(() -> new GraphQLInvalidArgumentException("Missing `currency` argument. You can use `" + PriceForSaleFieldHeaderDescriptor.CURRENCY.name() + "` parameter for specifying custom currency."));
}

@Nullable
protected OffsetDateTime resolveDesiredValidIn(@Nonnull DataFetchingEnvironment environment,
@Nonnull EntityDecorator entity,
@Nonnull EntityQueryContext context) {
@Nonnull EntityDecorator entity,
@Nonnull EntityQueryContext context) {
return Optional.ofNullable((OffsetDateTime) environment.getArgument(PriceForSaleFieldHeaderDescriptor.VALID_IN.name()))
.or(() -> Optional.ofNullable((Boolean) environment.getArgument(PriceForSaleFieldHeaderDescriptor.VALID_NOW.name()))
.map(validNow -> validNow ? entity.getAlignedNow() : null))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/FgForrest/evitaDB/blob/main/LICENSE
* https://github.com/FgForrest/evitaDB/blob/master/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -32,7 +32,6 @@
import io.evitadb.api.requestResponse.data.structure.EntityDecorator;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.dto.PrefetchedPriceForSale;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.AccompanyingPriceFieldHeaderDescriptor;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.PriceForSaleFieldHeaderDescriptor;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.resolver.dataFetcher.EntityQueryContext;
import io.evitadb.externalApi.graphql.exception.GraphQLInternalError;
import io.evitadb.externalApi.graphql.exception.GraphQLInvalidArgumentException;
Expand Down Expand Up @@ -100,7 +99,7 @@ private String resolvePriceName(@Nonnull DataFetchingEnvironment environment) {

@Nonnull
private Optional<PriceContract> getPrefetchedPrice(@Nonnull PrefetchedPriceForSale prefetchedPrices,
@Nonnull String priceName) {
@Nonnull String priceName) {
final Optional<PriceContract> prefetchedPrice = prefetchedPrices.getAccompanyingPrices().get(priceName);
if (prefetchedPrice == null) {
throw new GraphQLInternalError("Missing prefetched price `" + priceName + "` for entity `" + prefetchedPrices.getParentEntity().getType() + ":" + prefetchedPrices.getParentEntity().getPrimaryKey() + "`.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,63 +23,42 @@

package io.evitadb.externalApi.graphql.api.catalog.dataApi.resolver.dataFetcher.entity;

import graphql.execution.DataFetcherResult;
import graphql.schema.DataFetchingEnvironment;
import io.evitadb.api.requestResponse.data.PriceContract;
import io.evitadb.api.requestResponse.data.PricesContract.AccompanyingPrice;
import io.evitadb.api.requestResponse.data.PricesContract.PriceForSaleWithAccompanyingPrices;
import io.evitadb.api.requestResponse.data.structure.EntityDecorator;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.dto.PrefetchedPriceForSale;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.PriceForSaleFieldHeaderDescriptor;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.resolver.dataFetcher.EntityQueryContext;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.OffsetDateTime;
import java.util.Currency;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

/**
* Finds all prices for sale either by price predicate on fetched {@link EntityDecorator} or by explicitly set arguments.
* If not explicit arguments are present, the ones from query are used otherwise these argument have higher priority.
*
* @author Lukáš Hornych, FG Forrest a.s. (c) 2022
*/
public class AllPricesForSaleDataFetcher extends AbstractPriceForSaleDataFetcher<List<PriceContract>> {
public class AllPricesForSaleDataFetcher extends AbstractPriceForSaleDataFetcher<List<? extends PriceContract>> {

@Nonnull
@Nullable
@Override
public DataFetcherResult<List<PriceContract>> get(@Nonnull DataFetchingEnvironment environment) throws Exception {
final EntityDecorator entity = environment.getSource();
final EntityQueryContext context = environment.getLocalContext();

final String[] priceLists = resolveDesiredPricesLists(environment, context);
final Currency currency = resolveDesiredCurrency(environment, context);
final OffsetDateTime validIn = resolveDesiredValidIn(environment, entity, context);
final AccompanyingPrice[] desiredAccompanyingPrices = resolveDesiredAccompanyingPrices(environment);

final Optional<List<PriceForSaleWithAccompanyingPrices>> priceForSale = entity.getAllPricesForSaleWithAccompanyingPrices(
currency,
validIn,
priceLists,
protected List<? extends PriceContract> computePrices(@Nonnull EntityDecorator entity,
@Nonnull String[] desiredPriceLists,
@Nonnull Currency desiredCurrency,
@Nullable OffsetDateTime desiredValidIn,
@Nonnull AccompanyingPrice[] desiredAccompanyingPrices) {
final List<PriceForSaleWithAccompanyingPrices> pricesForSale = entity.getAllPricesForSaleWithAccompanyingPrices(
desiredCurrency,
desiredValidIn,
desiredPriceLists,
desiredAccompanyingPrices
);

final Locale customLocale = environment.getArgument(PriceForSaleFieldHeaderDescriptor.LOCALE.name());
final EntityQueryContext newContext = context.toBuilder()
.desiredPriceInPriceLists(priceLists)
.desiredPriceInCurrency(currency)
.desiredPriceValidIn(validIn)
.desiredPriceValidInNow(false)
.desiredLocale(customLocale != null ? customLocale : context.getDesiredLocale())
.build();

return DataFetcherResult.<PriceContract>newResult()
.data(priceForSale
.map(it -> new PrefetchedPriceForSale(it.priceForSale(), entity, it.accompanyingPrices()))
.orElse(null))
.localContext(newContext)
.build();
return pricesForSale.stream()
.map(it -> new PrefetchedPriceForSale(it.priceForSale(), entity, it.accompanyingPrices()))
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,16 @@

package io.evitadb.externalApi.graphql.api.catalog.dataApi.resolver.dataFetcher.entity;

import graphql.execution.DataFetcherResult;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import io.evitadb.api.requestResponse.data.PriceContract;
import io.evitadb.api.requestResponse.data.PricesContract.AccompanyingPrice;
import io.evitadb.api.requestResponse.data.PricesContract.PriceForSaleWithAccompanyingPrices;
import io.evitadb.api.requestResponse.data.structure.EntityDecorator;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.dto.PrefetchedPriceForSale;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.AccompanyingPriceFieldHeaderDescriptor;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.PriceForSaleDescriptor;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.PriceForSaleFieldHeaderDescriptor;
import io.evitadb.externalApi.graphql.api.catalog.dataApi.resolver.dataFetcher.EntityQueryContext;
import io.evitadb.externalApi.graphql.api.resolver.SelectionSetAggregator;
import io.evitadb.externalApi.graphql.exception.GraphQLInvalidArgumentException;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.OffsetDateTime;
import java.util.Currency;
import java.util.Locale;
import java.util.Optional;

/**
Expand All @@ -53,39 +43,22 @@
*/
public class PriceForSaleDataFetcher extends AbstractPriceForSaleDataFetcher<PriceContract> {

@Nonnull
@Nullable
@Override
public DataFetcherResult<PriceContract> get(@Nonnull DataFetchingEnvironment environment) throws Exception {
final EntityDecorator entity = environment.getSource();
final EntityQueryContext context = environment.getLocalContext();

final String[] priceLists = resolveDesiredPricesLists(environment, context);
final Currency currency = resolveDesiredCurrency(environment, context);
final OffsetDateTime validIn = resolveDesiredValidIn(environment, entity, context);
final AccompanyingPrice[] desiredAccompanyingPrices = resolveDesiredAccompanyingPrices(environment);

protected PriceContract computePrices(@Nonnull EntityDecorator entity,
@Nonnull String[] desiredPriceLists,
@Nonnull Currency desiredCurrency,
@Nullable OffsetDateTime desiredValidIn,
@Nonnull AccompanyingPrice[] desiredAccompanyingPrices) {
final Optional<PriceForSaleWithAccompanyingPrices> priceForSale = entity.getPriceForSaleWithAccompanyingPrices(
currency,
validIn,
priceLists,
desiredCurrency,
desiredValidIn,
desiredPriceLists,
desiredAccompanyingPrices
);

final Locale customLocale = environment.getArgument(PriceForSaleFieldHeaderDescriptor.LOCALE.name());
final EntityQueryContext newContext = context.toBuilder()
.desiredPriceInPriceLists(priceLists)
.desiredPriceInCurrency(currency)
.desiredPriceValidIn(validIn)
.desiredPriceValidInNow(false)
.desiredLocale(customLocale != null ? customLocale : context.getDesiredLocale())
.build();

return DataFetcherResult.<PriceContract>newResult()
.data(priceForSale
.map(it -> new PrefetchedPriceForSale(it.priceForSale(), entity, it.accompanyingPrices()))
.orElse(null))
.localContext(newContext)
.build();
return priceForSale
.map(it -> new PrefetchedPriceForSale(it.priceForSale(), entity, it.accompanyingPrices()))
.orElse(null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,17 @@ public DataFetcherResult<Collection<PriceContract>> get(@Nonnull DataFetchingEnv
final EntityDecorator entity = environment.getSource();
final Collection<PriceContract> prices;
if (priceLists == null && currency == null) {
prices = entity.getPrefetchedPrices();
prices = entity.getPrices();
} else if (priceLists != null && currency != null) {
prices = priceLists.stream()
.flatMap(pl -> entity.getPrefetchedPrices(currency, pl).stream())
.flatMap(pl -> entity.getPrices(currency, pl).stream())
.toList();
} else if (priceLists != null) {
prices = priceLists.stream()
.flatMap(pl -> entity.getPrefetchedPrices(pl).stream())
.flatMap(pl -> entity.getPrices(pl).stream())
.toList();
} else {
prices = entity.getPrefetchedPrices(currency);
prices = entity.getPrices(currency);
}

final Locale customLocale = environment.getArgument(PricesFieldHeaderDescriptor.LOCALE.name());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
* Copyright (c) 2023
* Copyright (c) 2023-2024
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -50,6 +50,9 @@ public GraphQLArgument.Builder apply(@Nonnull PropertyDescriptor propertyDescrip
.name(propertyDescriptor.name())
.description(propertyDescriptor.description());

if (propertyDescriptor.deprecate() != null) {
argumentBuilder.deprecate(propertyDescriptor.deprecate());
}
if (propertyDescriptor.type() != null) {
final GraphQLInputType graphQLType = (GraphQLInputType) propertyDataTypeTransformer.apply(propertyDescriptor.type());
argumentBuilder.type(graphQLType);
Expand Down
Loading

0 comments on commit 5ea0c0c

Please sign in to comment.