/// <summary> /// Helper function to extract the aggs for a simple (e.g. non-toolType) aggregation /// </summary> /// <returns>The simple aggs.</returns> /// <param name="facetConfig">Configuration for the field being aggregating</param> /// <param name="res">Res.</param> private IEnumerable <KeyLabelAggResult> ExtractAggResults(R4RAPIOptions.FacetConfig facetConfig, ISearchResponse <Resource> res) { var currBucket = res.Aggs.Nested($"{facetConfig.FilterName}_agg"); //We need to go one level deeper if this has a dependent filter if (!String.IsNullOrWhiteSpace(facetConfig.RequiresFilter)) { currBucket = currBucket.Filter($"{facetConfig.FilterName}_filter"); } var keys = currBucket.Terms($"{facetConfig.FilterName}_key"); foreach (var keyBucket in keys.Buckets) { long count = keyBucket.DocCount ?? 0; string key = keyBucket.Key; var label = ""; var labelBuckets = keyBucket.Terms($"{facetConfig.FilterName}_label").Buckets; if (labelBuckets.Count > 0) { label = labelBuckets.First().Key; } yield return(new KeyLabelAggResult() { Key = key, Label = label, Count = count }); } }
/// <summary> /// Transforms the string name and returned aggregate results into a Facet /// </summary> /// <returns>An facet</returns> /// <param name="facetToFetch">The name of the facet to fetch</param> /// <param name="resourceQuery">The resource query (used for determining currently-set filters)</param> /// <param name="aggResults">The list of aggregate results to transform to facet items</param> private Facet TransformFacet(string facetToFetch, ResourceQuery resourceQuery, KeyLabelAggResult[] aggResults) { R4RAPIOptions.FacetConfig config = _apiOptions.AvailableFacets[facetToFetch]; IEnumerable <string> filters = new string[] { }; if (resourceQuery.Filters.ContainsKey(config.FilterName)) { filters = resourceQuery.Filters[config.FilterName]; } Facet facet = new Facet { Param = config.FilterName, Title = config.Label, Items = aggResults.Select(fi => new FacetItem { Key = fi.Key, Label = fi.Label, Count = Convert.ToInt32(fi.Count), Selected = filters.Contains(fi.Key) }).ToArray() }; if (config.FacetType == R4RAPIOptions.FacetTypes.Single && IsFilterSet(config.FilterName, resourceQuery)) { facet.Items = facet.Items.Where(i => i.Selected).ToArray(); } return(facet); }
/// <summary> /// Determines whether a facet should be fetched /// This returns false if a facet requires a different filter to be set, and that other filter isn't set /// </summary> /// <returns>A boolean, indicating whether to fetch the given facet</returns> /// <param name="facetName">A list of facet names to include in our search</param> /// <param name="resourceQuery">The resource query (used for determining currently-set filters)</param> private bool ShouldFetchFacet(string facetName, ResourceQuery resourceQuery) { R4RAPIOptions.FacetConfig config = _apiOptions.AvailableFacets[facetName]; var filters = (!string.IsNullOrWhiteSpace(config.RequiresFilter) && resourceQuery.Filters.ContainsKey(config.RequiresFilter)) ? resourceQuery.Filters[config.RequiresFilter] : new string[] { }; if (!string.IsNullOrWhiteSpace(config.RequiresFilter) && filters.Length == 0) { return(false); } else { return(true); } }
/// <summary> /// Gets the simple aggregation "query" /// </summary> /// <returns>An AggregationDictionary containing the "query"</returns> /// <param name="facetConfig">Configuration for the field to aggregate</param> /// <param name="query"></param> private AggregationDictionary GetAggQuery(R4RAPIOptions.FacetConfig facetConfig, ResourceQuery query) { // If we *really* need the parentKey for this facet, then we must add it to the aggregation. // however, we may not really need it. var keyLabelAggregate = new TermsAggregation($"{facetConfig.FilterName}_key") { Field = new Field($"{facetConfig.FilterName}.key"), //Set the field to rollup Size = 999, //Use a large number to indicate unlimted (def is 10) // Now, we need to get the labels for the keys and thus // we need to add a sub aggregate for this term. // Normally you would do this for something like city/state rollups Aggregations = new TermsAggregation($"{facetConfig.FilterName}_label") { Field = new Field($"{facetConfig.FilterName}.label") } }; AggregationDictionary aggBody = keyLabelAggregate; // This facet requires a parent and thus needs a filter aggregate // to wrap the keyLabelAggregate if (!String.IsNullOrWhiteSpace(facetConfig.RequiresFilter)) { aggBody = new FilterAggregation($"{facetConfig.FilterName}_filter") { Filter = this.GetQueryForFilterField($"{facetConfig.FilterName}.parentKey", query.Filters[facetConfig.RequiresFilter]), Aggregations = keyLabelAggregate }; } //Start with a nested aggregation var agg = new NestedAggregation($"{facetConfig.FilterName}_agg") { Path = new Field(facetConfig.FilterName), //Set the path of the nested agg // Add the sub aggregates (bucket keys) Aggregations = aggBody }; return(agg); }
/// <summary> /// Asynchronously gets the key label aggregation for a field /// </summary> /// <param name="field">The field to aggregate</param> /// <param name="query">The query for the results</param> /// <returns>The aggregation items</returns> public async Task <KeyLabelAggResult[]> GetKeyLabelAggregationAsync(string field, ResourceQuery query) { if (string.IsNullOrWhiteSpace(field)) { throw new ArgumentNullException(nameof(field)); } if (query == null) { throw new ArgumentNullException(nameof(query)); } if (!this._apiOptions.AvailableFacets.ContainsKey(field)) { throw new ArgumentException($"Field, {field}, does not have any configuration"); } //Get config R4RAPIOptions.FacetConfig facetConfig = this._apiOptions.AvailableFacets[field]; //Make sure we have a valid config. if (facetConfig == null) { throw new ArgumentNullException($"Facet configuration could not be found for {field}"); } if ( !string.IsNullOrWhiteSpace(facetConfig.RequiresFilter) && (!query.Filters.ContainsKey(facetConfig.RequiresFilter) || query.Filters[facetConfig.RequiresFilter].Length == 0) ) { throw new ArgumentException($"Facet, {facetConfig.FilterName}, requires filter, {facetConfig.RequiresFilter}"); } Indices index = Indices.Index(new string[] { this._apiOptions.AliasName }); Types types = Types.Type(new string[] { "resource" }); SearchRequest req = new SearchRequest(index, types) { Size = 0, //req.Size = 0; //Set the size to 0 in order to return no Resources // Let's check if the field has a period if it does, then we need // to handle it differently because it is either toolType.type or // toolType.subtype. Aggregations = GetAggQuery(facetConfig, query) }; var searchQuery = GetSearchQueryForFacet(facetConfig.FilterName, query); if (searchQuery != null) { req.Query = searchQuery; } try { //We must(?) pass the C# type to map the results to //even though our queries should not return anything. var res = await this._elasticClient.SearchAsync <Resource>(req); return(ExtractAggResults(facetConfig, res).ToArray()); } catch (Exception ex) { this._logger.LogError($"Could not fetch aggregates for field: {field}"); throw new Exception($"Could not fetch aggregates for field: {field}", ex); } }