Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Infra UI] Add new graphql endpoint for snapshot data #34264

Merged
merged 14 commits into from
Apr 18, 2019

Conversation

skh
Copy link
Contributor

@skh skh commented Apr 1, 2019

Summary

First part of implementation for #31405 -- add a new graphql endpoint with the new behaviour.

This is mostly a rewrite of the existing nodes endpoint. Once this change has been merged, the UI will be changed to use this new endpoint, and the old one can be removed. This will happen in #34938 .

Notable changes:

  • Simplified source code structure: no separation of adapter and domain code as we agreed we don't need this abstraction any more
  • Use of composite aggregation instead of terms aggregation with partitions. By this, generating the ES queries could be simplified.
  • Removed the option to group by filters (instead of fields) as it isn't used by the UI at all
  • Namespaced types that are only used in this part to InfraSnapshot*
  • Reduced the number of explicit types as much as possible

Testing

Test in GraphiQL.

Sample query (replace FROM, TO and HOSTNAME)

{
  source(id: "default") {
    snapshot (timerange: {from:FROM,interval:"1m",to:TO}, filterQuery:"{\"bool\":{\"should\":[{\"match_phrase\":{\"host.name\":\"HOSTNAME\"}}],\"minimum_should_match\":1}}") {
      nodes(type: host, groupby: [{field:"service.type", label:"Service"},{field:"agent.version"}], metric: {type: tx} ) {
        path {
          value
          label
        }
        metric {
          value
          avg
          max
          name
        }
      }
    }
  }
}
  • have test data from at least two nodes, if possible
  • set SNAPSHOT_COMPOSITE_REQUEST_SIZE in server/lib/snapshot/constants.ts to a number smaller than your number of nodes
  • enable debug logging (in your Kibana config, set elasticsearch.logQueries: true and logging.verbose: true) to verify that multiple queries are indeed performed
  • test various groupBys, metric types and filter queries

Checklist

Use strikethroughs to remove checklist items you don't feel are applicable to this PR.

- [ ] This was checked for cross-browser compatibility, including a check against IE11
- [ ] Any text added follows EUI's writing guidelines, uses sentence case text and includes i18n support
- [ ] Documentation was added for features that require explanation or tutorials

- [ ] Unit or functional tests were updated or added to match the most common scenarios

I would like to add tests in the implementation of #34718 because we use the query definitions from the UI containers in our functional tests, and this is where they will be added.

- [ ] This was checked for keyboard-only and screenreader accessibility

For maintainers

- [ ] This was checked for breaking API changes and was labeled appropriately
- [ ] This includes a feature addition or change that requires a release note and was labeled appropriately

This is ready for review now, or together with #34938 -- whichever you prefer.

@skh skh self-assigned this Apr 1, 2019
@skh skh force-pushed the infraui-new-snapshot-graphql branch from 9e98ac0 to 04e1ad5 Compare April 1, 2019 14:01
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@alvarolobato alvarolobato added the Feature:Metrics UI Metrics UI feature label Apr 1, 2019
@elasticmachine
Copy link
Contributor

Pinging @elastic/infrastructure-ui

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@skh skh changed the title Add new graphql endpoint for snapshot data [Infra UI] Add new graphql endpoint for snapshot data Apr 8, 2019
@skh skh added the v7.2.0 label Apr 8, 2019
@skh skh force-pushed the infraui-new-snapshot-graphql branch from d4da0f0 to bca3954 Compare April 9, 2019 14:07
@elasticmachine
Copy link
Contributor

💔 Build Failed

@skh skh force-pushed the infraui-new-snapshot-graphql branch 2 times, most recently from 0043d20 to 469f1c4 Compare April 9, 2019 15:02
@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@skh skh force-pushed the infraui-new-snapshot-graphql branch from 469f1c4 to 065e53b Compare April 9, 2019 15:57
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@elasticmachine
Copy link
Contributor

💔 Build Failed

@skh skh requested a review from simianhacker April 11, 2019 14:42
@skh skh marked this pull request as ready for review April 11, 2019 14:42
@elasticmachine
Copy link
Contributor

💔 Build Failed

@skh
Copy link
Contributor Author

skh commented Apr 15, 2019

jenkins, please test this

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Copy link
Member

@simianhacker simianhacker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some notes inline with the code...

Also, I would like to see an integration test for this as well. It looks like the original map test is tied directly with the UI query which makes it difficult to copy. I would probably copy it and modify it to use a hard coded query. Then update it to be consistent once the UI portion is available. I would leave a TODO comment next to the hard coded query OR file a follow up issue so we don't forget.

};

const mergeNodeMetrics = (
nodes: any[],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both nodes and metrics should use InfraSnapshotMetricResponse[] as the type. Also I would change the names to nodeBuckets and nodeBucketsWithMetrics.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They have different types, but you're right nevertheless.

const result: any[] = [];
const nodeMetricsForLookup = getNodeMetricsForLookup(metrics);

nodes.forEach(node => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change this to a map so that it doesn't side effect result and just return directly from the map. This will eliminate the any on line 186 as well.

return buckets;
};

const mergeNodeMetrics = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming this to mergeNodeBuckets?

},
};

let response = await framework.callWithRequest<any, any>(request, 'search', query);
Copy link
Member

@simianhacker simianhacker Apr 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 94-108 are identical to lines 164-178. How about we create a function named getAllNodeBuckets which return InfraSnapshotMetricResponse[]? I also think we need to defined the Aggregation response type and pass it to the second type argument on the function call, something like:

interface InfraSnapshotAggregation {
  buckets: InfraSnapshotMetricResponse[];
  after_key: { [id: string]: string };
}

interface InfraSnapshotAggregationResponse {
  nodes: InfraSnapshotAggregation;
}
const response = framework.callWithRequest<{}, InfraSnapshotAggregationResponse>(request, 'search', query);

When you provide an aggregation type, the generics that back up that request will return a type that defines the aggregation key as optional which will then require a check to make sure aggregations are return. We can't count on the response to always have that key set.

I would also like to see the let and side effecting while loop factored out of this code. If you combine lines 94-108 and 164-178 in to one function. You could write that function to recurse until there are no more results, then return all the buckets. Maybe something like this:

const getAllNodesBuckets = async (
  framework: InfraBackendFrameworkAdapter,
  req: InfraFrameworkRequest,
  requestQuery: InfraSnapshotNodeRequest, // this type needs to be created
  previousBuckets: InfraSnapshotMetricResponse[] = []
): Promise<InfraSnapshotMetricResponse[]> => {
  const response = await framework.callWithRequest<{}, InfraSnapshotAggregationResponse>(
    req,
    'search',
    requestQuery
  );

  // Nothing available, return the previous buckets.
  if (response.hits.total.value === 0) {
    return previousBuckets;
  }

    // if ES doesn't return aggregations key, we can either throw an error or return previousBuckets.
  if (!response.aggregations) {
    throw new Error('Whoops!, `aggregations` key must always be returned.');
  }

  const currentBuckets = response.aggregations.nodes.buckets;

  // if the buckets length is zero then we are finished paginating through the results
  if (currentBuckets.length === 0) {
    return previousBuckets;
  }

  // create a new request object
  const newQuery = {
    ...requestQuery,
    body: {
      ...requestQuery.body,
      aggs: {
        ...requestQuery.body.aggs,
        nodes: {
          ...requestQuery.body.aggs.nodes,
          composite: {
            ...requestQuery.body.aggs.nodes.composite,
            after: response.aggregations.nodes.after_key,
          },
        },
      },
    },
  };

  // request additional buckets
  return getAllNodesBuckets(framework, req, newQuery, [...previousBuckets, ...currentBuckets]);
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Thanks.

import { getIntervalInSeconds } from '../../utils/get_interval_in_seconds';
import { InfraSnapshotRequestOptions } from './snapshot';

export interface InfraSnapshotMetricResponse {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we rename this to InfraSnapshotNodeBucket

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I'll refine the type names in that file.


export const getGroupedNodesSources = (options: InfraSnapshotRequestOptions) => {
const sources = [];
options.groupby.forEach(gb => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we use a map here with a trailing push for the last source definition and then return the results directly from the function? This needs a return type as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on the map, but I'm not sure about the return type -- this function is used only once and only factored out for readability. Adding an explicit type only adds mental overload here in my opinion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only recommended the type because Typescript will likely complain about the object/key signature. If it works without the type then it should be fine.

) => {
const path = [];
const node = groupBucket.key;
options.groupby.forEach(gb => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to a map with a push for the trailing value and return directly from the function.


export const getNodeMetricsForLookup = (metrics: InfraSnapshotMetricResponse[]) => {
const nodeMetricsForLookup: any = {};
metrics.forEach(metric => {
Copy link
Member

@simianhacker simianhacker Apr 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we use a reduce here to transform the array into a hash? Also we should define a return type for the parent function.

"Nodes of type host, container or pod grouped by 0, 1 or 2 terms"
nodes(
type: InfraNodeType!
groupby: [InfraSnapshotGroupbyInput!]!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change groupby to groupBy to be consistent

Suggested change
groupby: [InfraSnapshotGroupbyInput!]!
groupBy: [InfraSnapshotGroupbyInput!]!

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@skh
Copy link
Contributor Author

skh commented Apr 18, 2019

Also, I would like to see an integration test for this as well. It looks like the original map test is tied directly with the UI query which makes it difficult to copy. I would probably copy it and modify it to use a hard coded query. Then update it to be consistent once the UI portion is available. I would leave a TODO comment next to the hard coded query OR file a follow up issue so we don't forget.

That's why I wrote in the original description:

I would like to add tests in the implementation of #34718 because we use the query definitions from the UI containers in our functional tests, and this is where they will be added.

I'm actually already working on these tests, but it will be easier to add them in the next PR (which is based on this one). Would this work for you?

@skh skh force-pushed the infraui-new-snapshot-graphql branch from 9daf16d to c475797 Compare April 18, 2019 11:03
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@skh
Copy link
Contributor Author

skh commented Apr 18, 2019

@simianhacker thanks for your review! I've responded to all your comments, this is ready for another look.

Copy link
Member

@simianhacker simianhacker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes. Good Job!

 ___       ________ _________  _____ ______      
|\  \     |\   ____\\___   ___\\   _ \  _   \    
\ \  \    \ \  \___\|___ \  \_\ \  \\\__\ \  \   
 \ \  \    \ \  \  ___  \ \  \ \ \  \\|__| \  \  
  \ \  \____\ \  \|\  \  \ \  \ \ \  \    \ \  \ 
   \ \_______\ \_______\  \ \__\ \ \__\    \ \__\
    \|_______|\|_______|   \|__|  \|__|     \|__|
                                                 

@skh skh merged commit bff10d2 into elastic:master Apr 18, 2019
@skh skh mentioned this pull request Apr 25, 2019
2 tasks
skh added a commit to skh/kibana that referenced this pull request Apr 30, 2019
* Add new graphql endpoint for snapshot data

* Polishing.

* Keep type generic that is used outside snapshots

* Keep one more generic type generic

* Use camelCase for consistency.

* Refine type names

* Add return types.

* Use idiomatic javascript.

* Factor out getAllCompositeAggregationData<T>()

* Refine naming.

* More idiomatic JavaScript, more types.
skh added a commit that referenced this pull request Apr 30, 2019
…35792)

* [Infra UI] Add new graphql endpoint for snapshot data (#34264)

* Add new graphql endpoint for snapshot data

* Polishing.

* Keep type generic that is used outside snapshots

* Keep one more generic type generic

* Use camelCase for consistency.

* Refine type names

* Add return types.

* Use idiomatic javascript.

* Factor out getAllCompositeAggregationData<T>()

* Refine naming.

* More idiomatic JavaScript, more types.

* Use pre-8.x response format (see also #30826)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants