snpkg-snapi-elasticsearch-resolver-builder

Graphql resolver builder for elasticsearch based nodes

Usage no npm install needed!

<script type="module">
  import snpkgSnapiElasticsearchResolverBuilder from 'https://cdn.skypack.dev/snpkg-snapi-elasticsearch-resolver-builder';
</script>

README

About

Build elastic search resolvers

Install

Install by referencing the github location and the release number:

npm install --save social-native/snpkg-snapi-elasticsearch-resolver-builder#v0.1.0

Run locally

  1. Run the migrations NODE_ENV=development npm run migrate:latest
  2. Seed the database NODE_ENV=development npm run seed:run
  3. Run the dev server npm run dev
  4. Visit the GraphQL playground http://localhost:4000/graphql
  5. Run some queries!
query {
  campaign(id: 2143) {
    aggregation {
      percentageHistogram {
        audienceCity {
          valueFormat
          nameFormat
          data {
            name
            value
          }
        }
        audienceState {
          data {
            name
            value
          }
        }
        audienceCountry {
          data {
            name
            value
          }
        }
      }
      sum {
        comments {
          total
        }
        reach {
          total
        }
        likes {
          total
        }
        engagements {
          total
        }
        engagementRate {
          total
        }
        contents {
          total
        }
        creators {
          total
        }
        
      }
      distributed {
        likes(input: { interval: 10000 }) {
          data {
            x
            y
          }
        }
        engagements(input: { interval: 10000 }) {
          data {
            x
            y
          }
        }
        
        engagementRate(input: { interval: 100, since: "0", until: "20000000000" }) {
          data {
            x
            y
          }
        }
        
        reach(input: { interval: 10000 }) {
          data {
            x
            y
          }
        }
        
        comments(input: { interval: 10000 }) {
          data {
            x
            y
          }
        }
         }
      cumulative {
        likes(input: { interval: 10000 }) {
          data {
            x
            y
          }
        }
        engagements(input: { interval: 10000 }) {
          data {
            x
            y
          }
        }
        
        engagementRate(input: { interval: 10000 }) {
          data {
            x
            y
          }
        }
        
        reach(input: { interval: 10000 }) {
          data {
            x
            y
          }
        }
        
        comments(input: { interval: 10000 }) {
          data {
            x
            y
          }
        }
         }
    }
  }
}

Defining a resolver builder template:

  1. Extend the ResolverBuilderBase
import {Base} from 'snpkg-snapi-elasticsearch-resolver-builder';

class SomeBuilder extends Base{}
  1. Define setState, queryBuilder, and nodeBuilder in the template
class SomeBuilder extends ResolverBase {
    protected setState({args}: IResolverInputObj): IState {
        return {
            range: this.calcRange(args.input || {}),
            // 60 minutes is the default bucket size
            interval: args && args.input && args.input.interval ? args.input.interval : 60
        };
    }

    protected buildQuery(
        {parent: {id}}: IResolverInputObj,
        config: ICumulativePercentageConfig,
        state: IState
    ) {
        return {
            body: {
                size: 0,
                query: {
                    bool: {
                        filter: [
                            {term: {campaign_id: id}},
                            {range: {metric_since_timestamp: state.range}}
                        ]
                    }
                },
                sort: [{indexed_timestamp: 'desc'}],
                aggs: {
                    per_day: {
                        date_histogram: {
                            field: 'indexed_timestamp',
                            interval: `${state.interval}m`,
                            format: 'yyyy-MM-dd',
                            min_doc_count: 0
                        },
                        ...etc
                    }
                }
            }
        };
    }

    protected buildNode(result: IQueryResult, state: IState) {
        const parsedData = (get(result, 'body.aggregations.per_day.buckets') ||
            []) as ICumulativePercentBucket[];
        ...etc
        return <GQLNodeType>;
    }
}

How to use

  1. Build a query executor
import {DefaultQueryExecutor} from 'snpkg-snapi-elasticsearch-resolver-builder';

const queryExecutor = new DefaultQueryExecutor(ELASTIC_SEARCH_INDEX, elasticSearchClient);
  1. Initialize one or more of the reusable elastic search resolver builders or use one of the builders you defined in a template
import {builders} from 'snpkg-snapi-elasticsearch-resolver-builder';

const sumESResolver = new builders.Sum({queryExecutor});
const uniqueCountESResolver = new builders.UniqueCount({queryExecutor});
const distributedESResolver = new builders.Distributed({queryExecutor});
const cumulativeESResolver = new builders.Cumulative({queryExecutor});
const cumulativePercentESResolver = new builders.CumulativePercent({queryExecutor});
const distributedPercentESResolver = new builders.DistributedPercent({queryExecutor});
const sumPercentESResolver = new builders.SumPercent({queryExecutor});
const percentHistogramESResolver = new builders.PercentHistogram({queryExecutor});
  1. Build one or more resolvers
const resolvers = {
    sum: {
        comments: sumESResolver.buildResolver({fieldToSum: 'comments'}),
        engagementRate: sumPercentESResolver.buildResolver({
            numeratorFieldToAggregateOver: 'engagements',
            denominatorFieldToAggregateOver: 'reach'
        }),
        engagements: sumESResolver.buildResolver({fieldToSum: 'engagements'}),
        likes: sumESResolver.buildResolver({fieldToSum: 'likes'}),
        reach: sumESResolver.buildResolver({fieldToSum: 'reach'}),
        creators: uniqueCountESResolver.buildResolver({fieldToUniquelyCount: 'creator_id'}),
        contents: uniqueCountESResolver.buildResolver({fieldToUniquelyCount: 'content_post_id'})
    },
    distributed: {
        comments: distributedESResolver.buildResolver({fieldToAggregateOver: 'comments'}),
        engagementRate: distributedPercentESResolver.buildResolver({
            numeratorFieldToAggregateOver: 'engagements',
            denominatorFieldToAggregateOver: 'reach'
        }),
        engagements: distributedESResolver.buildResolver({fieldToAggregateOver: 'engagements'}),
        likes: distributedESResolver.buildResolver({fieldToAggregateOver: 'likes'}),
        reach: distributedESResolver.buildResolver({fieldToAggregateOver: 'reach'})
    },
    cumulative: {
        comments: cumulativeESResolver.buildResolver({fieldToAggregateOver: 'comments'}),
        engagementRate: cumulativePercentESResolver.buildResolver({
            numeratorFieldToAggregateOver: 'engagements',
            denominatorFieldToAggregateOver: 'reach'
        }),
        engagements: cumulativeESResolver.buildResolver({fieldToAggregateOver: 'engagements'}),
        likes: cumulativeESResolver.buildResolver({fieldToAggregateOver: 'likes'}),
        reach: cumulativeESResolver.buildResolver({fieldToAggregateOver: 'reach'})
    },
    percentageHistogram: {
        audienceCountry: percentHistogramESResolver.buildResolver({
            numeratorFieldToBucketBy: 'audience_geography_countries.name',
            numeratorFieldToCount: 'audience_geography_countries.count',
            denominatorFieldToAggregateOver: 'audience_age.count',
            nameFormat: percentHistogramESResolver.nameFormats.ISO_COUNTRY_CODE
        }),
        audienceCity: percentHistogramESResolver.buildResolver({
            numeratorFieldToBucketBy: 'audience_geography_cities.name',
            numeratorFieldToCount: 'audience_geography_cities.count',
            denominatorFieldToAggregateOver: 'audience_age.count',
            nameFormat: percentHistogramESResolver.nameFormats.RAW
        }),
        audienceState: percentHistogramESResolver.buildResolver({
            numeratorFieldToBucketBy: 'audience_geography_states.name',
            numeratorFieldToCount: 'audience_geography_states.count',
            denominatorFieldToAggregateOver: 'audience_age.count',
            nameFormat: percentHistogramESResolver.nameFormats.RAW
        })
    }
};