Understanding GraphQL Custom Queries

In some cases, a custom function is needed to perform a specific operation/transformation on data (like manually calculating an average).

To do so the Platform offers the possibility to implement custom methods on the Data Service extension that will be automatically exposed via GraphQL, by using specific annotations on Java classes and methods. No custom mutation can be defined at the moment.

Note:

Note that, by default the web client is not allowing you to connect Data Grids to a custom query of an entity type. If you want to allow the user to configure Data Grids to use custom types, you need to activate this feature through the Application Preferences Menu

  • TABLE_SHOW_CUSTOM_TYPES: false (default) or true

  • PIVOT_SHOW_CUSTOM_TYPES: false (default) or true

The first step to create a custom GraphQL query is to create a class and annotate it with @GraphQLCustomDataService.

Every method of the bean annotated with GraphQLCustomDataSource will correspond to a GraphQL custom query.

Any annotated method first argument should be of type GeneContext, which contains context data such as scenarioId. GeneContext is filled and sent automatically via the web client components, without any specific configuration.

To add extra arguments to the custom query we can use the annotation @GraphQLCustomArgument.

For a given entity Country defined as follows:

public class Country {
    public String name;
    public int population;
}
  • Example 1:

    If we want to return the total population of all the countries defined in the database, one would define the following bean, to perform the custom calculation and return the result:

    @GraphQLCustomDataService
    public class CountryCustomService { 
    
        private final CountryRepository countryRepository;
    
        @GraphQLCustomDataSource(graphqlName = "getCustomTotalPopulation")
        public Integer getTotalPopulation(GeneContext uiContext)
        {
            String scenarioId = GeneContextUtils.getScenarioId(uiContext);
            return countryRepository
                    .findAllByDbGeneInternalScenarioId(scenarioId)
                    .stream()
                    .map(Country::getPopulation)
                    .reduce(0, (a, b) -> a + b);
        }
    }

    From this definition a new GraphQL schema query field is added where:

    • graphqlName: is the name generated for the new custom query.

    Using such custom definition, one can, for instance, create an interface that displays the sum of all the populations of all the countries. For more details, please refer to Chapter Understanding the Platform Web Client.

    The GraphQL query that the such a widget would implement should look like the following:

    query { getTotalPopulation( geneContext: {scenarioIds: ["3fc2af54-f3a9-46f3-8e1d-68fa3b6a6ef9"]} ) }
  • Example 2:

    If we want a version of the previous method that multiplies the total population result with a given value, we would add the following method to CountryCustomService class:

        @GraphQLCustomDataSource(graphqlName = "getCustomTotalPopulationTransformed")
        public Integer getTotalPopulationTransformed(GeneContext uiContext,
                                                     @GraphQLCustomArgument(graphQLName = "multiplyBy") Integer arg1)
        {
            return getTotalPopulation(uiContext) * arg1;
        }

    Where:

    • GraphQLCustomArgument.graphQlName is the name of the argument as it will be defined in GraphQL. Note that a different name can be used for the java method argument.

    To multiply populations by 10, the syntax is as follows:

    query { getCustomTotalPopulationTransformed(geneContext: {scenarioIds: ["3fc2af54-f3a9-46f3-8e1d-68fa3b6a6ef9"]}, multiplyBy: 10)}
  • Example 3:

    Similar to previous examples, if we want to write a custom GraphQL query that returns a top 3 list of the most populated countries, we would add the following method to CountryCustomService class:

        @GraphQLCustomDataSource(graphqlName = "getTop3CountriesByPopulation")
            public List<Country> getTop3CountriesByPopulation(GeneContext geneContext)
            {
                String scenarioId = GeneContextUtils.getScenarioId(geneContext);
                return countryRepository
                    .findAllByDbGeneInternalScenarioId(scenarioId)
                    .stream()
                    .sorted(Comparator.comparing(Country::getPopulation).reversed())
                    .limit(3)
                    .collect(Collectors.toList());
            }

    To query this custom GraphQL method, we can use the following syntax:

    query { 
         getTop3CountriesByPopulation( geneContext: {scenarioIds: ["3fc2af54-f3a9-46f3-8e1d-68fa3b6a6ef9"]} ) 
                                        { 
                                             id, 
                                             population 
                                         } 
    }
  • Example 4:

    It is also possible to return a paginated list of countries, having the same structure returned by the default query getEntity1Connection, as described above. To do so the following method is added to CountryCustomService class:

        @GraphQLCustomDataSource(graphqlName = "getPaginatedCountry")
            public Page<Country> getPaginatedCountry(GeneContext geneContext, PageRequest pageRequest) {
                String scenarioId = GeneContextUtils.getScenarioId(geneContext);
    
                return countryRepository.findAllByDbGeneInternalScenarioId(scenarioId, pageRequest);
            }

    PageRequest structure is the same as the one used for getEntity1Connection, as described above.

    The query would have the following syntax:

    query {getPaginatedCountry(geneContext: {scenarioIds: ["3fc2af54-f3a9-46f3-8e1d-68fa3b6a6ef9"]}, paginationRequest: {scenarioId: "3fc2af54-f3a9-46f3-8e1d-68fa3b6a6ef9", page: 0, size: 100, sort: [{property: "id", orderBy: DESC}]}){ totalPages, content{id,population}, totalElements }}