From 172c9144a8ce582264a64ce7941c6a45f535af91 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Thu, 25 Oct 2018 15:14:39 -0400 Subject: [PATCH 1/9] add basic structure for secops application --- .../secops/common/graphql/introspection.json | 873 ++++++++++++++++++ .../secops/common/graphql/root/index.ts | 7 + .../secops/common/graphql/root/schema.gql.ts | 18 + .../secops/common/graphql/typed_resolvers.ts | 77 ++ x-pack/plugins/secops/common/graphql/types.ts | 43 + x-pack/plugins/secops/index.ts | 47 + x-pack/plugins/secops/package.json | 18 + x-pack/plugins/secops/public/app.ts | 7 + .../plugins/secops/public/apps/kibana_app.ts | 11 + .../plugins/secops/public/apps/start_app.tsx | 42 + .../plugins/secops/public/apps/testing_app.ts | 9 + .../plugins/secops/public/components/page.tsx | 26 + .../secops/public/pages/home/index.tsx | 18 + .../plugins/secops/public/register_feature.ts | 22 + x-pack/plugins/secops/public/routes.tsx | 28 + .../plugins/secops/scripts/combined_schema.ts | 16 + .../scripts/generate_types_from_graphql.js | 49 + x-pack/plugins/secops/scripts/gql_gen.json | 11 + x-pack/plugins/secops/server/graphql/index.ts | 10 + .../secops/server/graphql/sources/index.ts | 8 + .../server/graphql/sources/resolvers.ts | 56 ++ .../server/graphql/sources/schema.gql.ts | 21 + x-pack/plugins/secops/server/init_server.ts | 19 + x-pack/plugins/secops/server/kibana.index.ts | 39 + .../secops/server/lib/compose/kibana.ts | 58 ++ .../server/lib/configuration/adapter_types.ts | 9 + .../secops/server/lib/configuration/index.ts | 7 + .../inmemory_configuration_adapter.ts | 16 + .../kibana_configuration_adapter.test.ts | 40 + .../kibana_configuration_adapter.ts | 75 ++ .../server/lib/framework/adapter_types.ts | 30 + .../lib/framework/apollo_server_hapi.ts | 129 +++ .../secops/server/lib/framework/index.ts | 7 + .../lib/framework/kibana_framework_adapter.ts | 75 ++ .../server/lib/sources/adapter_types.ts | 21 + .../configuration_sources_adapter.test.ts | 111 +++ .../sources/configuration_sources_adapter.ts | 63 ++ .../secops/server/lib/sources/index.ts | 47 + x-pack/plugins/secops/server/lib/types.ts | 32 + x-pack/plugins/secops/yarn.lock | 81 ++ 40 files changed, 2276 insertions(+) create mode 100644 x-pack/plugins/secops/common/graphql/introspection.json create mode 100644 x-pack/plugins/secops/common/graphql/root/index.ts create mode 100644 x-pack/plugins/secops/common/graphql/root/schema.gql.ts create mode 100644 x-pack/plugins/secops/common/graphql/typed_resolvers.ts create mode 100644 x-pack/plugins/secops/common/graphql/types.ts create mode 100644 x-pack/plugins/secops/index.ts create mode 100644 x-pack/plugins/secops/package.json create mode 100644 x-pack/plugins/secops/public/app.ts create mode 100644 x-pack/plugins/secops/public/apps/kibana_app.ts create mode 100644 x-pack/plugins/secops/public/apps/start_app.tsx create mode 100644 x-pack/plugins/secops/public/apps/testing_app.ts create mode 100644 x-pack/plugins/secops/public/components/page.tsx create mode 100644 x-pack/plugins/secops/public/pages/home/index.tsx create mode 100644 x-pack/plugins/secops/public/register_feature.ts create mode 100644 x-pack/plugins/secops/public/routes.tsx create mode 100644 x-pack/plugins/secops/scripts/combined_schema.ts create mode 100644 x-pack/plugins/secops/scripts/generate_types_from_graphql.js create mode 100644 x-pack/plugins/secops/scripts/gql_gen.json create mode 100644 x-pack/plugins/secops/server/graphql/index.ts create mode 100644 x-pack/plugins/secops/server/graphql/sources/index.ts create mode 100644 x-pack/plugins/secops/server/graphql/sources/resolvers.ts create mode 100644 x-pack/plugins/secops/server/graphql/sources/schema.gql.ts create mode 100644 x-pack/plugins/secops/server/init_server.ts create mode 100644 x-pack/plugins/secops/server/kibana.index.ts create mode 100644 x-pack/plugins/secops/server/lib/compose/kibana.ts create mode 100644 x-pack/plugins/secops/server/lib/configuration/adapter_types.ts create mode 100644 x-pack/plugins/secops/server/lib/configuration/index.ts create mode 100644 x-pack/plugins/secops/server/lib/configuration/inmemory_configuration_adapter.ts create mode 100644 x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.test.ts create mode 100644 x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.ts create mode 100644 x-pack/plugins/secops/server/lib/framework/adapter_types.ts create mode 100644 x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts create mode 100644 x-pack/plugins/secops/server/lib/framework/index.ts create mode 100644 x-pack/plugins/secops/server/lib/framework/kibana_framework_adapter.ts create mode 100644 x-pack/plugins/secops/server/lib/sources/adapter_types.ts create mode 100644 x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.test.ts create mode 100644 x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.ts create mode 100644 x-pack/plugins/secops/server/lib/sources/index.ts create mode 100644 x-pack/plugins/secops/server/lib/types.ts create mode 100644 x-pack/plugins/secops/yarn.lock diff --git a/x-pack/plugins/secops/common/graphql/introspection.json b/x-pack/plugins/secops/common/graphql/introspection.json new file mode 100644 index 0000000000000..55fa825f2f240 --- /dev/null +++ b/x-pack/plugins/secops/common/graphql/introspection.json @@ -0,0 +1,873 @@ +{ + "__schema": { + "queryType": { "name": "Query" }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "OBJECT", + "name": "Query", + "description": "", + "fields": [ + { + "name": "source", + "description": "Get an infrastructure data source by id", + "args": [ + { + "name": "id", + "description": "The id of the source", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "Source", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "allSources", + "description": "Get a list of all infrastructure data sources", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "Source", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ID", + "description": + "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Source", + "description": "", + "fields": [ + { + "name": "id", + "description": "The id of the source", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": + "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields": [ + { + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": + "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": + "If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": "A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Directive", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": + "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [ + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "ENUM", "name": "__TypeKind", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Field", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__EnumValue", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given `__Type` is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": + "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": + "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": + "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": + "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": + "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": + "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": + "A GraphQL-formatted string representing the default value for this input value.", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": + "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": + "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "ENUM", "name": "__DirectiveLocation", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onOperation", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onFragment", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onField", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": + "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Location adjacent to a field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Location adjacent to a schema definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Location adjacent to a scalar definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Location adjacent to an object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to an argument definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Location adjacent to an interface definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Location adjacent to a union definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Location adjacent to an enum definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + } + ], + "directives": [ + { + "name": "skip", + "description": + "Directs the executor to skip this field or fragment when the `if` argument is true.", + "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], + "args": [ + { + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "defaultValue": null + } + ] + }, + { + "name": "include", + "description": + "Directs the executor to include this field or fragment only when the `if` argument is true.", + "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], + "args": [ + { + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "defaultValue": null + } + ] + }, + { + "name": "deprecated", + "description": "Marks an element of a GraphQL schema as no longer supported.", + "locations": ["FIELD_DEFINITION", "ENUM_VALUE"], + "args": [ + { + "name": "reason", + "description": + "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": "\"No longer supported\"" + } + ] + } + ] + } +} diff --git a/x-pack/plugins/secops/common/graphql/root/index.ts b/x-pack/plugins/secops/common/graphql/root/index.ts new file mode 100644 index 0000000000000..47417b6376307 --- /dev/null +++ b/x-pack/plugins/secops/common/graphql/root/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { rootSchema } from './schema.gql'; diff --git a/x-pack/plugins/secops/common/graphql/root/schema.gql.ts b/x-pack/plugins/secops/common/graphql/root/schema.gql.ts new file mode 100644 index 0000000000000..0819f2e2808b8 --- /dev/null +++ b/x-pack/plugins/secops/common/graphql/root/schema.gql.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import gql from 'graphql-tag'; + +export const rootSchema = gql` + schema { + query: Query + #mutation: Mutation + } + + type Query + + #type Mutation +`; diff --git a/x-pack/plugins/secops/common/graphql/typed_resolvers.ts b/x-pack/plugins/secops/common/graphql/typed_resolvers.ts new file mode 100644 index 0000000000000..b3c4274f7f1e1 --- /dev/null +++ b/x-pack/plugins/secops/common/graphql/typed_resolvers.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GraphQLResolveInfo } from 'graphql'; + +type BasicResolver = ( + parent: any, + args: Args, + context: any, + info: GraphQLResolveInfo +) => Promise | Result; + +type AppResolverResult = + | Promise + | Promise<{ [P in keyof R]: () => Promise }> + | { [P in keyof R]: () => Promise } + | { [P in keyof R]: () => R[P] } + | R; + +export type AppResolvedResult = Resolver extends AppResolver + ? Result + : never; + +export type SubsetResolverWithFields = R extends BasicResolver< + Array, + infer ArgsInArray +> + ? BasicResolver< + Array>>, + ArgsInArray + > + : R extends BasicResolver + ? BasicResolver>, Args> + : never; + +export type SubsetResolverWithoutFields = R extends BasicResolver< + Array, + infer ArgsInArray +> + ? BasicResolver< + Array>>, + ArgsInArray + > + : R extends BasicResolver + ? BasicResolver>, Args> + : never; + +export type AppResolver = ( + parent: Parent, + args: Args, + context: Context, + info: GraphQLResolveInfo +) => AppResolverResult; + +export type AppResolverOf = Resolver extends BasicResolver< + infer Result, + infer Args +> + ? AppResolver + : never; + +export type AppResolverWithFields< + Resolver, + Parent, + Context, + IncludedFields extends string +> = AppResolverOf, Parent, Context>; + +export type AppResolverWithoutFields< + Resolver, + Parent, + Context, + ExcludedFields extends string +> = AppResolverOf, Parent, Context>; diff --git a/x-pack/plugins/secops/common/graphql/types.ts b/x-pack/plugins/secops/common/graphql/types.ts new file mode 100644 index 0000000000000..feb424adb2367 --- /dev/null +++ b/x-pack/plugins/secops/common/graphql/types.ts @@ -0,0 +1,43 @@ +/* tslint:disable */ +import { GraphQLResolveInfo } from 'graphql'; + +type Resolver = ( + parent: any, + args: Args, + context: any, + info: GraphQLResolveInfo +) => Promise | Result; + +export interface Query { + source: Source /** Get an infrastructure data source by id */; + allSources: Source[] /** Get a list of all infrastructure data sources */; +} + +export interface Source { + id: string /** The id of the source */; +} + +export namespace QueryResolvers { + export interface Resolvers { + source?: SourceResolver /** Get an infrastructure data source by id */; + allSources?: AllSourcesResolver /** Get a list of all infrastructure data sources */; + } + + export type SourceResolver = Resolver; + export interface SourceArgs { + id: string /** The id of the source */; + } + + export type AllSourcesResolver = Resolver; +} + +export namespace SourceResolvers { + export interface Resolvers { + id?: IdResolver /** The id of the source */; + } + + export type IdResolver = Resolver; +} +export interface SourceQueryArgs { + id: string /** The id of the source */; +} diff --git a/x-pack/plugins/secops/index.ts b/x-pack/plugins/secops/index.ts new file mode 100644 index 0000000000000..90f50e7f31534 --- /dev/null +++ b/x-pack/plugins/secops/index.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import JoiNamespace from 'joi'; +import { resolve } from 'path'; + +import { getConfigSchema, initServerWithKibana, KbnServer } from './server/kibana.index'; + +const APP_ID = 'secops'; + +export function secops(kibana: any) { + return new kibana.Plugin({ + id: APP_ID, + configPrefix: 'xpack.secops', + publicDir: resolve(__dirname, 'public'), + require: ['kibana', 'elasticsearch'], + uiExports: { + app: { + description: 'Explore your security operations', + main: 'plugins/secops/app', + title: 'Sec Ops', + listed: false, + url: `/app/${APP_ID}`, + }, + home: ['plugins/secops/register_feature'], + links: [ + { + description: 'Explore your security operations', + euiIconType: 'SecurityApp', + id: 'secops', + order: 9000, + title: 'Sec Ops', + url: `/app/${APP_ID}`, + }, + ], + }, + config(Joi: typeof JoiNamespace) { + return getConfigSchema(Joi); + }, + init(server: KbnServer) { + initServerWithKibana(server); + }, + }); +} diff --git a/x-pack/plugins/secops/package.json b/x-pack/plugins/secops/package.json new file mode 100644 index 0000000000000..22acc10b52c89 --- /dev/null +++ b/x-pack/plugins/secops/package.json @@ -0,0 +1,18 @@ +{ + "author": "Elastic", + "name": "SecOps", + "version": "7.0.0-alpha1", + "scripts": { + "build-graphql-types": "node scripts/generate_types_from_graphql.js" + }, + "devDependencies": { + "@types/boom": "3.2.2", + "@types/color": "^3.0.0", + "@types/lodash": "^4.14.110", + "@types/recompose": "^0.27.0" + }, + "dependencies": { + "boom": "3.1.1", + "lodash": "^4.17.10" + } +} diff --git a/x-pack/plugins/secops/public/app.ts b/x-pack/plugins/secops/public/app.ts new file mode 100644 index 0000000000000..255c51c9e48ce --- /dev/null +++ b/x-pack/plugins/secops/public/app.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import './apps/kibana_app'; diff --git a/x-pack/plugins/secops/public/apps/kibana_app.ts b/x-pack/plugins/secops/public/apps/kibana_app.ts new file mode 100644 index 0000000000000..ac705801175f3 --- /dev/null +++ b/x-pack/plugins/secops/public/apps/kibana_app.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import 'uiExports/autocompleteProviders'; + +import { compose } from '../lib/compose/kibana_compose'; +import { startApp } from './start_app'; +startApp(compose()); diff --git a/x-pack/plugins/secops/public/apps/start_app.tsx b/x-pack/plugins/secops/public/apps/start_app.tsx new file mode 100644 index 0000000000000..da32e65e8ffb0 --- /dev/null +++ b/x-pack/plugins/secops/public/apps/start_app.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createHashHistory } from 'history'; +import React from 'react'; +import { ApolloProvider } from 'react-apollo'; +import { Provider as ReduxStoreProvider } from 'react-redux'; +import { BehaviorSubject } from 'rxjs'; +import { pluck } from 'rxjs/operators'; +import { ThemeProvider } from 'styled-components'; + +// TODO use theme provided from parentApp when kibana supports it +import { EuiErrorBoundary } from '@elastic/eui'; +import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json'; +import { InfraFrontendLibs } from '../lib/lib'; +import { PageRouter } from '../routes'; +import { createStore } from '../store'; + +export async function startApp(libs: InfraFrontendLibs) { + const history = createHashHistory(); + + const libs$ = new BehaviorSubject(libs); + const store = createStore({ + apolloClient: libs$.pipe(pluck('apolloClient')), + observableApi: libs$.pipe(pluck('observableApi')), + }); + + libs.framework.render( + + + + + + + + + + ); +} diff --git a/x-pack/plugins/secops/public/apps/testing_app.ts b/x-pack/plugins/secops/public/apps/testing_app.ts new file mode 100644 index 0000000000000..bcd7d0e592644 --- /dev/null +++ b/x-pack/plugins/secops/public/apps/testing_app.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { compose } from '../lib/compose/testing_compose'; +import { startApp } from './start_app'; +startApp(compose()); diff --git a/x-pack/plugins/secops/public/components/page.tsx b/x-pack/plugins/secops/public/components/page.tsx new file mode 100644 index 0000000000000..04d69fdb1d9e8 --- /dev/null +++ b/x-pack/plugins/secops/public/components/page.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiPage } from '@elastic/eui'; +import styled from 'styled-components'; + +export const ColumnarPage = styled.div` + display: flex; + flex-direction: column; + align-items: stretch; + flex: 1 0 0; +`; + +export const PageContent = styled.div` + flex: 1 0 0; + display: flex; + flex-direction: row; + background-color: ${props => props.theme.eui.euiColorEmptyShade}; +`; + +export const FlexPage = styled(EuiPage)` + flex: 1 0 0; +`; diff --git a/x-pack/plugins/secops/public/pages/home/index.tsx b/x-pack/plugins/secops/public/pages/home/index.tsx new file mode 100644 index 0000000000000..edcccfe7a0e22 --- /dev/null +++ b/x-pack/plugins/secops/public/pages/home/index.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { pure } from 'recompose'; + +import { ColumnarPage } from '../../components/page'; + +const Home: React.SFC = () => ( + +

Hello Sec Ops

+
+); + +export const HomePage = pure(Home); diff --git a/x-pack/plugins/secops/public/register_feature.ts b/x-pack/plugins/secops/public/register_feature.ts new file mode 100644 index 0000000000000..322ab435eef4f --- /dev/null +++ b/x-pack/plugins/secops/public/register_feature.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + FeatureCatalogueCategory, + FeatureCatalogueRegistryProvider, +} from 'ui/registry/feature_catalogue'; + +const APP_ID = 'infra'; + +FeatureCatalogueRegistryProvider.register(() => ({ + id: 'secops', + title: 'Sec Ops', + description: 'Explore security metrics and logs for events and alerts', + icon: 'securityApp', + path: `/app/${APP_ID}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, +})); diff --git a/x-pack/plugins/secops/public/routes.tsx b/x-pack/plugins/secops/public/routes.tsx new file mode 100644 index 0000000000000..8990ee5cd9f2c --- /dev/null +++ b/x-pack/plugins/secops/public/routes.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { History } from 'history'; +import React from 'react'; +import { Redirect, Route, Router, Switch } from 'react-router-dom'; +import { pure } from 'recompose'; + +import { NotFoundPage } from './pages/404'; +import { HomePage } from './pages/home'; + +interface RouterProps { + history: History; +} + +const Routes: React.SFC = ({ history }) => ( + + + + + + +); + +export PageRouter = pure(Routes); diff --git a/x-pack/plugins/secops/scripts/combined_schema.ts b/x-pack/plugins/secops/scripts/combined_schema.ts new file mode 100644 index 0000000000000..ec3628b2632c9 --- /dev/null +++ b/x-pack/plugins/secops/scripts/combined_schema.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { buildSchemaFromTypeDefinitions } from 'graphql-tools'; + +import { schemas as serverSchemas } from '../server/graphql'; + +export const schemas = [...serverSchemas]; + +// this default export is used to feed the combined types to the gql-gen tool +// which generates the corresponding typescript types +// tslint:disable-next-line:no-default-export +export default buildSchemaFromTypeDefinitions(schemas); diff --git a/x-pack/plugins/secops/scripts/generate_types_from_graphql.js b/x-pack/plugins/secops/scripts/generate_types_from_graphql.js new file mode 100644 index 0000000000000..f36979c159376 --- /dev/null +++ b/x-pack/plugins/secops/scripts/generate_types_from_graphql.js @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +const { join, resolve } = require('path'); +// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved +const { generate } = require('graphql-code-generator'); + +const GRAPHQL_GLOBS = [ + join('public', 'containers', '**', '*.gql_query.ts{,x}'), + join('public', 'store', '**', '*.gql_query.ts{,x}'), + join('common', 'graphql', '**', '*.gql_query.ts{,x}'), +]; +const CONFIG_PATH = resolve(__dirname, 'gql_gen.json'); +const OUTPUT_INTROSPECTION_PATH = resolve('common', 'graphql', 'introspection.json'); +const OUTPUT_TYPES_PATH = resolve('common', 'graphql', 'types.ts'); +const SCHEMA_PATH = resolve(__dirname, 'combined_schema.ts'); + +async function main() { + await generate( + { + args: GRAPHQL_GLOBS, + config: CONFIG_PATH, + out: OUTPUT_INTROSPECTION_PATH, + overwrite: true, + require: ['ts-node/register'], + schema: SCHEMA_PATH, + template: 'graphql-codegen-introspection-template', + }, + true + ); + await generate( + { + args: GRAPHQL_GLOBS, + config: CONFIG_PATH, + out: OUTPUT_TYPES_PATH, + overwrite: true, + schema: SCHEMA_PATH, + template: 'graphql-codegen-typescript-template', + }, + true + ); +} + +if (require.main === module) { + main(); +} diff --git a/x-pack/plugins/secops/scripts/gql_gen.json b/x-pack/plugins/secops/scripts/gql_gen.json new file mode 100644 index 0000000000000..87b8233dd1eeb --- /dev/null +++ b/x-pack/plugins/secops/scripts/gql_gen.json @@ -0,0 +1,11 @@ +{ + "flattenTypes": true, + "generatorConfig": {}, + "primitives": { + "String": "string", + "Int": "number", + "Float": "number", + "Boolean": "boolean", + "ID": "string" + } +} diff --git a/x-pack/plugins/secops/server/graphql/index.ts b/x-pack/plugins/secops/server/graphql/index.ts new file mode 100644 index 0000000000000..8cf5394a91a6c --- /dev/null +++ b/x-pack/plugins/secops/server/graphql/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { rootSchema } from '../../common/graphql/root/schema.gql'; +import { sourcesSchema } from './sources/schema.gql'; + +export const schemas = [rootSchema, sourcesSchema]; diff --git a/x-pack/plugins/secops/server/graphql/sources/index.ts b/x-pack/plugins/secops/server/graphql/sources/index.ts new file mode 100644 index 0000000000000..ee187d8c31bec --- /dev/null +++ b/x-pack/plugins/secops/server/graphql/sources/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { createSourcesResolvers } from './resolvers'; +export { sourcesSchema } from './schema.gql'; diff --git a/x-pack/plugins/secops/server/graphql/sources/resolvers.ts b/x-pack/plugins/secops/server/graphql/sources/resolvers.ts new file mode 100644 index 0000000000000..1748e5f5092d2 --- /dev/null +++ b/x-pack/plugins/secops/server/graphql/sources/resolvers.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { QueryResolvers } from '../../../common/graphql/types'; +import { AppResolverWithFields } from '../../lib/framework'; +import { Sources } from '../../lib/sources'; +import { Context } from '../../lib/types'; + +export type QuerySourceResolver = AppResolverWithFields< + QueryResolvers.SourceResolver, + null, + Context, + 'id' | 'configuration' +>; + +export type QueryAllSourcesResolver = AppResolverWithFields< + QueryResolvers.AllSourcesResolver, + null, + Context, + 'id' | 'configuration' +>; + +interface SourcesResolversDeps { + sources: Sources; +} + +export const createSourcesResolvers = ( + libs: SourcesResolversDeps +): { + Query: { + source: QuerySourceResolver; + allSources: QueryAllSourcesResolver; + }; +} => ({ + Query: { + async source(root, args) { + const requestedSourceConfiguration = await libs.sources.getConfiguration(args.id); + + return { + id: args.id, + configuration: requestedSourceConfiguration, + }; + }, + async allSources() { + const sourceConfigurations = await libs.sources.getAllConfigurations(); + + return Object.entries(sourceConfigurations).map(([sourceName, sourceConfiguration]) => ({ + id: sourceName, + configuration: sourceConfiguration, + })); + }, + }, +}); diff --git a/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts b/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts new file mode 100644 index 0000000000000..d2372362e4594 --- /dev/null +++ b/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import gql from 'graphql-tag'; + +export const sourcesSchema = gql` + type Source { + "The id of the source" + id: ID! + } + + extend type Query { + "Get an infrastructure data source by id" + source("The id of the source" id: ID!): Source! + "Get a list of all infrastructure data sources" + allSources: [Source!]! + } +`; diff --git a/x-pack/plugins/secops/server/init_server.ts b/x-pack/plugins/secops/server/init_server.ts new file mode 100644 index 0000000000000..cfacf9f029dbc --- /dev/null +++ b/x-pack/plugins/secops/server/init_server.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IResolvers, makeExecutableSchema } from 'graphql-tools'; +import { schemas } from './graphql'; +import { createSourcesResolvers } from './graphql/sources'; +import { BackendLibs } from './lib/types'; + +export const initServer = (libs: BackendLibs) => { + const schema = makeExecutableSchema({ + resolvers: [createSourcesResolvers(libs) as IResolvers], + typeDefs: schemas, + }); + + libs.framework.registerGraphQLEndpoint('/api/secops/graphql', schema); +}; diff --git a/x-pack/plugins/secops/server/kibana.index.ts b/x-pack/plugins/secops/server/kibana.index.ts new file mode 100644 index 0000000000000..5ab9e7886fac4 --- /dev/null +++ b/x-pack/plugins/secops/server/kibana.index.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Server } from 'hapi'; +import JoiNamespace from 'joi'; +import { initServer } from './init_server'; +import { compose } from './lib/compose/kibana'; + +export interface KbnServer extends Server { + usage: any; +} + +export const initServerWithKibana = (kbnServer: KbnServer) => { + const libs = compose(kbnServer); + initServer(libs); +}; + +export const getConfigSchema = (Joi: typeof JoiNamespace) => { + const DefaultSourceConfigSchema = Joi.object({}); + + const InfraRootConfigSchema = Joi.object({ + enabled: Joi.boolean().default(true), + query: Joi.object({ + partitionSize: Joi.number(), + partitionFactor: Joi.number(), + }).default(), + sources: Joi.object() + .keys({ + default: DefaultSourceConfigSchema, + }) + .pattern(/.*/, DefaultSourceConfigSchema) + .default(), + }).default(); + + return InfraRootConfigSchema; +}; diff --git a/x-pack/plugins/secops/server/lib/compose/kibana.ts b/x-pack/plugins/secops/server/lib/compose/kibana.ts new file mode 100644 index 0000000000000..23c5c3a45bd23 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/compose/kibana.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Server } from 'hapi'; + +import { ElasticsearchCapabilitiesAdapter } from '../adapters/capabilities/elasticsearch_capabilities_adapter'; +import { InfraKibanaConfigurationAdapter } from '../adapters/configuration/kibana_configuration_adapter'; +import { FrameworkFieldsAdapter } from '../adapters/fields/framework_fields_adapter'; +import { InfraKibanaBackendFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { InfraKibanaLogEntriesAdapter } from '../adapters/log_entries/kibana_log_entries_adapter'; +import { KibanaMetricsAdapter } from '../adapters/metrics/kibana_metrics_adapter'; +import { ElasticsearchNodesAdapter } from '../adapters/nodes/elasticsearch_nodes_adapter'; +import { InfraElasticsearchSourceStatusAdapter } from '../adapters/source_status'; +import { InfraConfigurationSourcesAdapter } from '../adapters/sources/configuration_sources_adapter'; +import { InfraCapabilitiesDomain } from '../domains/capabilities_domain'; +import { InfraFieldsDomain } from '../domains/fields_domain'; +import { InfraLogEntriesDomain } from '../domains/log_entries_domain'; +import { InfraMetricsDomain } from '../domains/metrics_domain'; +import { InfraNodesDomain } from '../domains/nodes_domain'; +import { InfraBackendLibs, InfraConfiguration, InfraDomainLibs } from '../infra_types'; +import { InfraSourceStatus } from '../source_status'; +import { InfraSources } from '../sources'; + +export function compose(server: Server): InfraBackendLibs { + const configuration = new InfraKibanaConfigurationAdapter(server); + const framework = new InfraKibanaBackendFrameworkAdapter(server); + const sources = new InfraSources(new InfraConfigurationSourcesAdapter(configuration)); + const sourceStatus = new InfraSourceStatus(new InfraElasticsearchSourceStatusAdapter(framework), { + sources, + }); + + const domainLibs: InfraDomainLibs = { + capabilities: new InfraCapabilitiesDomain(new ElasticsearchCapabilitiesAdapter(framework), { + sources, + }), + fields: new InfraFieldsDomain(new FrameworkFieldsAdapter(framework), { + sources, + }), + logEntries: new InfraLogEntriesDomain(new InfraKibanaLogEntriesAdapter(framework), { + sources, + }), + nodes: new InfraNodesDomain(new ElasticsearchNodesAdapter(framework)), + metrics: new InfraMetricsDomain(new KibanaMetricsAdapter(framework)), + }; + + const libs: InfraBackendLibs = { + configuration, + framework, + sources, + sourceStatus, + ...domainLibs, + }; + + return libs; +} diff --git a/x-pack/plugins/secops/server/lib/configuration/adapter_types.ts b/x-pack/plugins/secops/server/lib/configuration/adapter_types.ts new file mode 100644 index 0000000000000..69938e400aaef --- /dev/null +++ b/x-pack/plugins/secops/server/lib/configuration/adapter_types.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface ConfigurationAdapter { + get(): Promise; +} diff --git a/x-pack/plugins/secops/server/lib/configuration/index.ts b/x-pack/plugins/secops/server/lib/configuration/index.ts new file mode 100644 index 0000000000000..4e09b5d0e9e2d --- /dev/null +++ b/x-pack/plugins/secops/server/lib/configuration/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './adapter_types'; diff --git a/x-pack/plugins/secops/server/lib/configuration/inmemory_configuration_adapter.ts b/x-pack/plugins/secops/server/lib/configuration/inmemory_configuration_adapter.ts new file mode 100644 index 0000000000000..d574b95755ebc --- /dev/null +++ b/x-pack/plugins/secops/server/lib/configuration/inmemory_configuration_adapter.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ConfigurationAdapter } from './adapter_types'; + +export class InmemoryConfigurationAdapter + implements ConfigurationAdapter { + constructor(private readonly configuration: Configuration) {} + + public async get() { + return this.configuration; + } +} diff --git a/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.test.ts b/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.test.ts new file mode 100644 index 0000000000000..8624df9d52891 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaConfigurationAdapter } from './kibana_configuration_adapter'; + +describe('the KibanaConfigurationAdapter', () => { + test('queries the xpack.infra configuration of the server', async () => { + const mockConfig = { + get: jest.fn(), + }; + + const configurationAdapter = new KibanaConfigurationAdapter({ + config: () => mockConfig, + }); + + await configurationAdapter.get(); + + expect(mockConfig.get).toBeCalledWith('xpack.infra'); + }); + + test('applies the query defaults', async () => { + const configurationAdapter = new KibanaConfigurationAdapter({ + config: () => ({ + get: () => ({}), + }), + }); + + const configuration = await configurationAdapter.get(); + + expect(configuration).toMatchObject({ + query: { + partitionSize: expect.any(Number), + partitionFactor: expect.any(Number), + }, + }); + }); +}); diff --git a/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.ts b/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.ts new file mode 100644 index 0000000000000..291c0677cf035 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; + +import { ConfigurationAdapter } from './adapter_types'; + +export class KibanaConfigurationAdapter + implements ConfigurationAdapter { + private readonly server: ServerWithConfig; + + constructor(server: any) { + if (!isServerWithConfig(server)) { + throw new Error('Failed to find configuration on server.'); + } + + this.server = server; + } + + public async get() { + const config = this.server.config(); + + if (!isKibanaConfiguration(config)) { + throw new Error('Failed to access configuration of server.'); + } + + const configuration = config.get('xpack.infra') || {}; + const configurationWithDefaults = { + enabled: true, + query: { + partitionSize: 75, + partitionFactor: 1.2, + ...(configuration.query || {}), + }, + sources: {}, + ...configuration, + } as Configuration; + + // we assume this to be the configuration because Kibana would have already validated it + return configurationWithDefaults; + } +} + +interface ServerWithConfig { + config(): any; +} + +function isServerWithConfig(maybeServer: any): maybeServer is ServerWithConfig { + return ( + Joi.validate( + maybeServer, + Joi.object({ + config: Joi.func().required(), + }).unknown() + ).error === null + ); +} + +interface KibanaConfiguration { + get(key: string): any; +} + +function isKibanaConfiguration(maybeConfiguration: any): maybeConfiguration is KibanaConfiguration { + return ( + Joi.validate( + maybeConfiguration, + Joi.object({ + get: Joi.func().required(), + }).unknown() + ).error === null + ); +} diff --git a/x-pack/plugins/secops/server/lib/framework/adapter_types.ts b/x-pack/plugins/secops/server/lib/framework/adapter_types.ts new file mode 100644 index 0000000000000..574d54abc16f7 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/framework/adapter_types.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GraphQLSchema } from 'graphql'; + +export * from '../../../common/graphql/typed_resolvers'; + +export const internalFrameworkRequest = Symbol('internalFrameworkRequest'); + +export interface FrameworkAdapter { + version: string; + exposeStaticDir(urlPath: string, dir: string): void; + registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void; +} + +export interface FrameworkRequest { + [internalFrameworkRequest]: InternalRequest; + payload: InternalRequest['payload']; + params: InternalRequest['params']; + query: InternalRequest['query']; +} + +export interface WrappableRequest { + payload: Payload; + params: Params; + query: Query; +} diff --git a/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts b/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts new file mode 100644 index 0000000000000..9277a9619fc4e --- /dev/null +++ b/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as GraphiQL from 'apollo-server-module-graphiql'; +import Boom from 'boom'; +import { IReply, Request, Server } from 'hapi'; + +import { GraphQLOptions, runHttpQuery } from 'apollo-server-core'; + +export interface IRegister { + (server: Server, options: any, next: () => void): void; + attributes: { + name: string; + version?: string; + }; +} + +export type HapiOptionsFunction = (req?: Request) => GraphQLOptions | Promise; + +export interface HapiPluginOptions { + path: string; + vhost?: string; + route?: any; + graphqlOptions: GraphQLOptions | HapiOptionsFunction; +} + +export const graphqlHapi: IRegister = Object.assign( + (server: Server, options: HapiPluginOptions, next: () => void) => { + if (!options || !options.graphqlOptions) { + throw new Error('Apollo Server requires options.'); + } + + server.route({ + config: options.route || {}, + handler: async (request: Request, reply: IReply) => { + try { + const gqlResponse = await runHttpQuery([request], { + method: request.method.toUpperCase(), + options: options.graphqlOptions, + query: request.method === 'post' ? request.payload : request.query, + }); + + return reply(gqlResponse).type('application/json'); + } catch (error) { + if ('HttpQueryError' !== error.name) { + const queryError = Boom.wrap(error); + + queryError.output.payload.message = error.message; + + return reply(queryError); + } + + if (error.isGraphQLError === true) { + return reply(error.message) + .code(error.statusCode) + .type('application/json'); + } + + const genericError = Boom.create(error.statusCode, error.message); + + if (error.headers) { + Object.keys(error.headers).forEach(header => { + genericError.output.headers[header] = error.headers[header]; + }); + } + + // Boom hides the error when status code is 500 + + genericError.output.payload.message = error.message; + + throw genericError; + } + }, + method: ['GET', 'POST'], + path: options.path || '/graphql', + vhost: options.vhost || undefined, + }); + + return next(); + }, + { + attributes: { + name: 'graphql', + }, + } +); + +export type HapiGraphiQLOptionsFunction = ( + req?: Request +) => GraphiQL.GraphiQLData | Promise; + +export interface HapiGraphiQLPluginOptions { + path: string; + + route?: any; + + graphiqlOptions: GraphiQL.GraphiQLData | HapiGraphiQLOptionsFunction; +} + +export const graphiqlHapi: IRegister = Object.assign( + (server: Server, options: HapiGraphiQLPluginOptions) => { + if (!options || !options.graphiqlOptions) { + throw new Error('Apollo Server GraphiQL requires options.'); + } + + server.route({ + config: options.route || {}, + handler: async (request: Request, reply: IReply) => { + const graphiqlString = await GraphiQL.resolveGraphiQLString( + request.query, + options.graphiqlOptions, + request + ); + + return reply(graphiqlString).type('text/html'); + }, + method: 'GET', + path: options.path || '/graphiql', + }); + }, + { + attributes: { + name: 'graphiql', + }, + } +); diff --git a/x-pack/plugins/secops/server/lib/framework/index.ts b/x-pack/plugins/secops/server/lib/framework/index.ts new file mode 100644 index 0000000000000..4e09b5d0e9e2d --- /dev/null +++ b/x-pack/plugins/secops/server/lib/framework/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './adapter_types'; diff --git a/x-pack/plugins/secops/server/lib/framework/kibana_framework_adapter.ts b/x-pack/plugins/secops/server/lib/framework/kibana_framework_adapter.ts new file mode 100644 index 0000000000000..17a8b8bc05925 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/framework/kibana_framework_adapter.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GraphQLSchema } from 'graphql'; +import { Request, Server } from 'hapi'; + +import { + FrameworkAdapter, + FrameworkRequest, + internalFrameworkRequest, + WrappableRequest, +} from './adapter_types'; +import { graphiqlHapi, graphqlHapi } from './apollo_server_hapi'; + +export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { + public version: string; + private server: Server; + + constructor(hapiServer: Server) { + this.server = hapiServer; + this.version = hapiServer.plugins.kibana.status.plugin.version; + } + + public exposeStaticDir(urlPath: string, dir: string): void { + this.server.route({ + handler: { + directory: { + path: dir, + }, + }, + method: 'GET', + path: urlPath, + }); + } + + public registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void { + this.server.register({ + options: { + graphqlOptions: (req: Request) => ({ + context: { req: wrapRequest(req) }, + schema, + }), + path: routePath, + }, + register: graphqlHapi, + }); + + this.server.register({ + options: { + graphiqlOptions: { + endpointURL: routePath, + passHeader: `'kbn-version': '${this.version}'`, + }, + path: `${routePath}/graphiql`, + }, + register: graphiqlHapi, + }); + } +} + +export function wrapRequest( + req: InternalRequest +): FrameworkRequest { + const { params, payload, query } = req; + + return { + [internalFrameworkRequest]: req, + params, + payload, + query, + }; +} diff --git a/x-pack/plugins/secops/server/lib/sources/adapter_types.ts b/x-pack/plugins/secops/server/lib/sources/adapter_types.ts new file mode 100644 index 0000000000000..39672654e8562 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/sources/adapter_types.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SourceConfiguration } from './index'; + +export type PartialSourceConfigurations = { + default?: PartialDefaultSourceConfiguration; +} & { + [sourceId: string]: PartialSourceConfiguration; +}; + +export type PartialDefaultSourceConfiguration = { + fields?: Partial; +} & Partial>>; + +export type PartialSourceConfiguration = { + fields?: Partial; +} & Pick>; diff --git a/x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.test.ts b/x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.test.ts new file mode 100644 index 0000000000000..76e12166cdd31 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.test.ts @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { InmemoryConfigurationAdapter } from '../configuration/inmemory_configuration_adapter'; +import { PartialSourceConfiguration } from './adapter_types'; +import { ConfigurationSourcesAdapter } from './configuration_sources_adapter'; + +describe('the ConfigurationSourcesAdapter', () => { + test('adds the default source when no sources are configured', async () => { + const sourcesAdapter = new ConfigurationSourcesAdapter( + new InmemoryConfigurationAdapter({ sources: {} }) + ); + + expect(await sourcesAdapter.getAll()).toMatchObject({ + default: { + metricAlias: expect.any(String), + logAlias: expect.any(String), + fields: { + container: expect.any(String), + host: expect.any(String), + message: expect.arrayContaining([expect.any(String)]), + pod: expect.any(String), + tiebreaker: expect.any(String), + timestamp: expect.any(String), + }, + }, + }); + }); + + test('adds missing aliases to default source when they are missing from the configuration', async () => { + const sourcesAdapter = new ConfigurationSourcesAdapter( + new InmemoryConfigurationAdapter({ + sources: { + default: {} as PartialSourceConfiguration, + }, + }) + ); + + expect(await sourcesAdapter.getAll()).toMatchObject({ + default: { + metricAlias: expect.any(String), + logAlias: expect.any(String), + }, + }); + }); + + test('adds missing fields to default source when they are missing from the configuration', async () => { + const sourcesAdapter = new ConfigurationSourcesAdapter( + new InmemoryConfigurationAdapter({ + sources: { + default: { + metricAlias: 'METRIC_ALIAS', + logAlias: 'LOG_ALIAS', + fields: { + container: 'DIFFERENT_CONTAINER_FIELD', + }, + } as PartialSourceConfiguration, + }, + }) + ); + + expect(await sourcesAdapter.getAll()).toMatchObject({ + default: { + metricAlias: 'METRIC_ALIAS', + logAlias: 'LOG_ALIAS', + fields: { + container: 'DIFFERENT_CONTAINER_FIELD', + host: expect.any(String), + message: expect.arrayContaining([expect.any(String)]), + pod: expect.any(String), + tiebreaker: expect.any(String), + timestamp: expect.any(String), + }, + }, + }); + }); + + test('adds missing fields to non-default sources when they are missing from the configuration', async () => { + const sourcesAdapter = new ConfigurationSourcesAdapter( + new InmemoryConfigurationAdapter({ + sources: { + sourceOne: { + metricAlias: 'METRIC_ALIAS', + logAlias: 'LOG_ALIAS', + fields: { + container: 'DIFFERENT_CONTAINER_FIELD', + }, + }, + }, + }) + ); + + expect(await sourcesAdapter.getAll()).toMatchObject({ + sourceOne: { + metricAlias: 'METRIC_ALIAS', + logAlias: 'LOG_ALIAS', + fields: { + container: 'DIFFERENT_CONTAINER_FIELD', + host: expect.any(String), + message: expect.arrayContaining([expect.any(String)]), + pod: expect.any(String), + tiebreaker: expect.any(String), + timestamp: expect.any(String), + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.ts b/x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.ts new file mode 100644 index 0000000000000..309ecffd4db94 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { ConfigurationAdapter } from '../configuration'; +import { PartialSourceConfigurations } from './adapter_types'; +import { SourceConfigurations, SourcesAdapter } from './index'; + +interface ConfigurationWithSources { + sources?: PartialSourceConfigurations; +} + +export class ConfigurationSourcesAdapter implements SourcesAdapter { + private readonly configuration: ConfigurationAdapter; + + constructor(configuration: ConfigurationAdapter) { + this.configuration = configuration; + } + + public async getAll() { + const sourceConfigurations = (await this.configuration.get()).sources || { + default: DEFAULT_SOURCE, + }; + const sourceConfigurationsWithDefault = { + ...sourceConfigurations, + default: { + ...DEFAULT_SOURCE, + ...(sourceConfigurations.default || {}), + }, + } as PartialSourceConfigurations; + + return Object.entries(sourceConfigurationsWithDefault).reduce( + (result, [sourceId, sourceConfiguration]) => + ({ + ...result, + [sourceId]: { + ...sourceConfiguration, + fields: { + ...DEFAULT_FIELDS, + ...(sourceConfiguration.fields || {}), + }, + }, + } as SourceConfigurations), + {} + ); + } +} + +const DEFAULT_FIELDS = { + container: 'docker.container.name', + host: 'beat.hostname', + message: ['message', '@message'], + pod: 'kubernetes.pod.name', + tiebreaker: '_doc', + timestamp: '@timestamp', +}; + +const DEFAULT_SOURCE = { + metricAlias: 'metricbeat-*', + logAlias: 'filebeat-*', + fields: DEFAULT_FIELDS, +}; diff --git a/x-pack/plugins/secops/server/lib/sources/index.ts b/x-pack/plugins/secops/server/lib/sources/index.ts new file mode 100644 index 0000000000000..b49ee5ef5cf87 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/sources/index.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ConfigurationSourcesAdapter } from './configuration_sources_adapter'; + +export class Sources { + constructor(private readonly adapter: SourcesAdapter) {} + + public async getConfiguration(sourceId: string) { + const sourceConfigurations = await this.getAllConfigurations(); + const requestedSourceConfiguration = sourceConfigurations[sourceId]; + + if (!requestedSourceConfiguration) { + throw new Error(`Failed to find source '${sourceId}'`); + } + + return requestedSourceConfiguration; + } + + public getAllConfigurations() { + return this.adapter.getAll(); + } +} + +export interface SourcesAdapter { + getAll(): Promise; +} + +export interface SourceConfigurations { + [sourceId: string]: SourceConfiguration; +} + +export interface SourceConfiguration { + metricAlias: string; + logAlias: string; + fields: { + container: string; + host: string; + message: string[]; + pod: string; + tiebreaker: string; + timestamp: string; + }; +} diff --git a/x-pack/plugins/secops/server/lib/types.ts b/x-pack/plugins/secops/server/lib/types.ts new file mode 100644 index 0000000000000..8f79dde4be45f --- /dev/null +++ b/x-pack/plugins/secops/server/lib/types.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ConfigurationAdapter } from './configuration'; +import { FrameworkAdapter, FrameworkRequest } from './framework'; +import { SourceConfigurations, Sources } from './sources'; + +export interface AppDomainLibs { + hello: string; +} + +export interface AppBackendLibs extends AppDomainLibs { + configuration: ConfigurationAdapter; + framework: FrameworkAdapter; + sources: Sources; +} + +export interface Configuration { + enabled: boolean; + query: { + partitionSize: number; + partitionFactor: number; + }; + sources: SourceConfigurations; +} + +export interface Context { + req: FrameworkRequest; +} diff --git a/x-pack/plugins/secops/yarn.lock b/x-pack/plugins/secops/yarn.lock new file mode 100644 index 0000000000000..ce8b8a3df33f0 --- /dev/null +++ b/x-pack/plugins/secops/yarn.lock @@ -0,0 +1,81 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/boom@3.2.2": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@types/boom/-/boom-3.2.2.tgz#6773bb1bbfec111f5ea683874b8d932157d58885" + integrity sha512-Wbgg2JXCnlEMWB2faZgT8x1MPPgzqqLBWx1zXXWGPDQDo9CcvQkIW89QqY+yekoXIKI+qW/eZsg/gv6+WNFwEg== + dependencies: + "@types/node" "*" + +"@types/color-convert@*": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-1.9.0.tgz#bfa8203e41e7c65471e9841d7e306a7cd8b5172d" + integrity sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg== + dependencies: + "@types/color-name" "*" + +"@types/color-name@*": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.0.tgz#926f76f7e66f49cc59ad880bb15b030abbf0b66d" + integrity sha512-gZ/Rb+MFXF0pXSEQxdRoPMm5jeO3TycjOdvbpbcpHX/B+n9AqaHFe5q6Ga9CsZ7ir/UgIWPfrBzUzn3F19VH/w== + +"@types/color@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.0.tgz#40f8a6bf2fd86e969876b339a837d8ff1b0a6e30" + integrity sha512-5qqtNia+m2I0/85+pd2YzAXaTyKO8j+svirO5aN+XaQJ5+eZ8nx0jPtEWZLxCi50xwYsX10xUHetFzfb1WEs4Q== + dependencies: + "@types/color-convert" "*" + +"@types/lodash@^4.14.110": + version "4.14.117" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.117.tgz#695a7f514182771a1e0f4345d189052ee33c8778" + integrity sha512-xyf2m6tRbz8qQKcxYZa7PA4SllYcay+eh25DN3jmNYY6gSTL7Htc/bttVdkqj2wfJGbeWlQiX8pIyJpKU+tubw== + +"@types/node@*": + version "10.12.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.0.tgz#ea6dcbddbc5b584c83f06c60e82736d8fbb0c235" + integrity sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ== + +"@types/prop-types@*": + version "15.5.6" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.6.tgz#9c03d3fed70a8d517c191b7734da2879b50ca26c" + integrity sha512-ZBFR7TROLVzCkswA3Fmqq+IIJt62/T7aY/Dmz+QkU7CaW2QFqAitCE8Ups7IzmGhcN1YWMBT4Qcoc07jU9hOJQ== + +"@types/react@*": + version "16.4.18" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.4.18.tgz#2e28a2e7f92d3fa7d6a65f2b73275c3e3138a13d" + integrity sha512-eFzJKEg6pdeaukVLVZ8Xb79CTl/ysX+ExmOfAAqcFlCCK5TgFDD9kWR0S18sglQ3EmM8U+80enjUqbfnUyqpdA== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + +"@types/recompose@^0.27.0": + version "0.27.0" + resolved "https://registry.yarnpkg.com/@types/recompose/-/recompose-0.27.0.tgz#66955b8ea4d477c3c03f09a571380ca82549cdac" + integrity sha512-p92Unulom3aSdgWCFXrKIpd6obaO3gOKJhEBJUBohlcC21aDXnsnu1BbYL42GeV5qcXezeRXM1SXiyKpnFSGlA== + dependencies: + "@types/react" "*" + +boom@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-3.1.1.tgz#b6424f01ed8d492b2b12ae86047c24e8b6a7c937" + integrity sha1-tkJPAe2NSSsrEq6GBHwk6LanyTc= + dependencies: + hoek "3.x.x" + +csstype@^2.2.0: + version "2.5.7" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.7.tgz#bf9235d5872141eccfb2d16d82993c6b149179ff" + integrity sha512-Nt5VDyOTIIV4/nRFswoCKps1R5CD1hkiyjBE9/thNaNZILLEviVw9yWQw15+O+CpNjQKB/uvdcxFFOrSflY3Yw== + +hoek@3.x.x: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-3.0.4.tgz#268adff66bb6695c69b4789a88b1e0847c3f3123" + integrity sha1-Jorf9mu2aVxptHiaiLHghHw/MSM= + +lodash@^4.17.10: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== From 7e27819075446c03c750b25269699c82da1a0498 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 26 Oct 2018 07:06:38 -0400 Subject: [PATCH 2/9] finalize skeleton for secops --- x-pack/.gitignore | 1 + x-pack/index.js | 2 + x-pack/plugins/secops/index.ts | 7 +- x-pack/plugins/secops/package.json | 2 +- .../plugins/secops/public/apps/start_app.tsx | 26 +-- .../framework/kibana_framework_adapter.ts | 184 ++++++++++++++++++ .../framework/testing_framework_adapter.ts | 29 +++ .../observable_api/kibana_observable_api.ts | 41 ++++ .../public/lib/compose/kibana_compose.ts | 69 +++++++ .../public/lib/compose/testing_compose.ts | 59 ++++++ x-pack/plugins/secops/public/lib/lib.ts | 72 +++++++ x-pack/plugins/secops/public/pages/404.tsx | 10 + .../secops/public/pages/home/index.tsx | 8 +- .../plugins/secops/public/register_feature.ts | 4 +- x-pack/plugins/secops/public/routes.tsx | 9 +- x-pack/plugins/secops/server/init_server.ts | 4 +- .../secops/server/lib/compose/kibana.ts | 50 ++--- .../lib/framework/apollo_server_hapi.ts | 4 +- 18 files changed, 504 insertions(+), 77 deletions(-) create mode 100644 x-pack/plugins/secops/public/lib/adapters/framework/kibana_framework_adapter.ts create mode 100644 x-pack/plugins/secops/public/lib/adapters/framework/testing_framework_adapter.ts create mode 100644 x-pack/plugins/secops/public/lib/adapters/observable_api/kibana_observable_api.ts create mode 100644 x-pack/plugins/secops/public/lib/compose/kibana_compose.ts create mode 100644 x-pack/plugins/secops/public/lib/compose/testing_compose.ts create mode 100644 x-pack/plugins/secops/public/lib/lib.ts create mode 100644 x-pack/plugins/secops/public/pages/404.tsx diff --git a/x-pack/.gitignore b/x-pack/.gitignore index eb1aec466bbfa..f9d4d71829fa5 100644 --- a/x-pack/.gitignore +++ b/x-pack/.gitignore @@ -10,3 +10,4 @@ /.env /.kibana-plugin-helpers.dev.* !/plugins/infra/**/target +!/plugins/secops/**/target diff --git a/x-pack/index.js b/x-pack/index.js index 374cef7d26b66..5aa5eaa95c9d7 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -28,6 +28,7 @@ import { kueryAutocomplete } from './plugins/kuery_autocomplete'; import { canvas } from './plugins/canvas'; import { infra } from './plugins/infra'; import { rollup } from './plugins/rollup'; +import { secops } from './plugins/secops'; module.exports = function (kibana) { return [ @@ -55,5 +56,6 @@ module.exports = function (kibana) { kueryAutocomplete(kibana), infra(kibana), rollup(kibana), + secops(kibana), ]; }; diff --git a/x-pack/plugins/secops/index.ts b/x-pack/plugins/secops/index.ts index 90f50e7f31534..e64cdb2ad80d7 100644 --- a/x-pack/plugins/secops/index.ts +++ b/x-pack/plugins/secops/index.ts @@ -21,19 +21,20 @@ export function secops(kibana: any) { app: { description: 'Explore your security operations', main: 'plugins/secops/app', + euiIconType: 'securityApp', title: 'Sec Ops', listed: false, - url: `/app/${APP_ID}`, + url: `/app/${APP_ID}#/home`, }, home: ['plugins/secops/register_feature'], links: [ { description: 'Explore your security operations', - euiIconType: 'SecurityApp', + euiIconType: 'securityApp', id: 'secops', order: 9000, title: 'Sec Ops', - url: `/app/${APP_ID}`, + url: `/app/${APP_ID}#/home`, }, ], }, diff --git a/x-pack/plugins/secops/package.json b/x-pack/plugins/secops/package.json index 22acc10b52c89..bc99a3e702693 100644 --- a/x-pack/plugins/secops/package.json +++ b/x-pack/plugins/secops/package.json @@ -1,6 +1,6 @@ { "author": "Elastic", - "name": "SecOps", + "name": "sec-ops", "version": "7.0.0-alpha1", "scripts": { "build-graphql-types": "node scripts/generate_types_from_graphql.js" diff --git a/x-pack/plugins/secops/public/apps/start_app.tsx b/x-pack/plugins/secops/public/apps/start_app.tsx index da32e65e8ffb0..0154531c3275c 100644 --- a/x-pack/plugins/secops/public/apps/start_app.tsx +++ b/x-pack/plugins/secops/public/apps/start_app.tsx @@ -7,36 +7,24 @@ import { createHashHistory } from 'history'; import React from 'react'; import { ApolloProvider } from 'react-apollo'; -import { Provider as ReduxStoreProvider } from 'react-redux'; -import { BehaviorSubject } from 'rxjs'; -import { pluck } from 'rxjs/operators'; import { ThemeProvider } from 'styled-components'; // TODO use theme provided from parentApp when kibana supports it import { EuiErrorBoundary } from '@elastic/eui'; import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json'; -import { InfraFrontendLibs } from '../lib/lib'; +import { AppFrontendLibs } from '../lib/lib'; import { PageRouter } from '../routes'; -import { createStore } from '../store'; -export async function startApp(libs: InfraFrontendLibs) { +export async function startApp(libs: AppFrontendLibs) { const history = createHashHistory(); - const libs$ = new BehaviorSubject(libs); - const store = createStore({ - apolloClient: libs$.pipe(pluck('apolloClient')), - observableApi: libs$.pipe(pluck('observableApi')), - }); - libs.framework.render( - - - - - - - + + + + + ); } diff --git a/x-pack/plugins/secops/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/secops/public/lib/adapters/framework/kibana_framework_adapter.ts new file mode 100644 index 0000000000000..e3c1bc9ff0072 --- /dev/null +++ b/x-pack/plugins/secops/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -0,0 +1,184 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IModule, IScope } from 'angular'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; + +import { UIRoutes as KibanaUIRoutes } from 'ui/routes'; + +import { + AppBufferedKibanaServiceCall, + AppFrameworkAdapter, + AppKibanaAdapterServiceRefs, + AppKibanaUIConfig, + AppTimezoneProvider, + AppUiKibanaAdapterScope, +} from '../../lib'; + +const ROOT_ELEMENT_ID = 'react-secops-root'; +const BREADCRUMBS_ELEMENT_ID = 'react-secops-breadcrumbs'; + +export class AppKibanaFrameworkAdapter implements AppFrameworkAdapter { + public appState: object; + public dateFormat?: string; + public kbnVersion?: string; + public scaledDateFormat?: string; + public timezone?: string; + + private adapterService: KibanaAdapterServiceProvider; + private timezoneProvider: AppTimezoneProvider; + private rootComponent: React.ReactElement | null = null; + private breadcrumbsComponent: React.ReactElement | null = null; + + constructor(uiModule: IModule, uiRoutes: KibanaUIRoutes, timezoneProvider: AppTimezoneProvider) { + this.adapterService = new KibanaAdapterServiceProvider(); + this.timezoneProvider = timezoneProvider; + this.appState = {}; + this.register(uiModule, uiRoutes); + } + + public setUISettings = (key: string, value: any) => { + this.adapterService.callOrBuffer(({ config }) => { + config.set(key, value); + }); + }; + + public render = (component: React.ReactElement) => { + this.adapterService.callOrBuffer(() => (this.rootComponent = component)); + }; + + public renderBreadcrumbs = (component: React.ReactElement) => { + this.adapterService.callOrBuffer(() => (this.breadcrumbsComponent = component)); + }; + + private register = (adapterModule: IModule, uiRoutes: KibanaUIRoutes) => { + adapterModule.provider('kibanaAdapter', this.adapterService); + + adapterModule.directive('appUiKibanaAdapter', () => ({ + controller: ($scope: AppUiKibanaAdapterScope, $element: JQLite) => ({ + $onDestroy: () => { + const targetRootElement = $element[0].querySelector(`#${ROOT_ELEMENT_ID}`); + const targetBreadcrumbsElement = $element[0].querySelector(`#${ROOT_ELEMENT_ID}`); + + if (targetRootElement) { + ReactDOM.unmountComponentAtNode(targetRootElement); + } + + if (targetBreadcrumbsElement) { + ReactDOM.unmountComponentAtNode(targetBreadcrumbsElement); + } + }, + $onInit: () => { + $scope.topNavMenu = []; + }, + $postLink: () => { + $scope.$watchGroup( + [ + () => this.breadcrumbsComponent, + () => $element[0].querySelector(`#${BREADCRUMBS_ELEMENT_ID}`), + ], + ([breadcrumbsComponent, targetElement]) => { + if (!targetElement) { + return; + } + + if (breadcrumbsComponent) { + ReactDOM.render(breadcrumbsComponent, targetElement); + } else { + ReactDOM.unmountComponentAtNode(targetElement); + } + } + ); + $scope.$watchGroup( + [() => this.rootComponent, () => $element[0].querySelector(`#${ROOT_ELEMENT_ID}`)], + ([rootComponent, targetElement]) => { + if (!targetElement) { + return; + } + + if (rootComponent) { + ReactDOM.render(rootComponent, targetElement); + } else { + ReactDOM.unmountComponentAtNode(targetElement); + } + } + ); + }, + }), + scope: true, + template: ` +
+ `, + })); + + adapterModule.run(( + config: AppKibanaUIConfig, + kbnVersion: string, + Private: (provider: Provider) => Provider, + // @ts-ignore: inject kibanaAdapter to force eager instatiation + kibanaAdapter: any + ) => { + this.timezone = Private(this.timezoneProvider)(); + this.kbnVersion = kbnVersion; + this.dateFormat = config.get('dateFormat'); + this.scaledDateFormat = config.get('dateFormat:scaled'); + }); + + uiRoutes.enable(); + + uiRoutes.otherwise({ + reloadOnSearch: false, + template: + '', + }); + }; +} + +// tslint:disable-next-line: max-classes-per-file +class KibanaAdapterServiceProvider { + public serviceRefs: AppKibanaAdapterServiceRefs | null = null; + public bufferedCalls: Array> = []; + + public $get($rootScope: IScope, config: AppKibanaUIConfig) { + this.serviceRefs = { + config, + rootScope: $rootScope, + }; + + this.applyBufferedCalls(this.bufferedCalls); + + return this; + } + + public callOrBuffer(serviceCall: (serviceRefs: AppKibanaAdapterServiceRefs) => void) { + if (this.serviceRefs !== null) { + this.applyBufferedCalls([serviceCall]); + } else { + this.bufferedCalls.push(serviceCall); + } + } + + public applyBufferedCalls( + bufferedCalls: Array> + ) { + if (!this.serviceRefs) { + return; + } + + this.serviceRefs.rootScope.$apply(() => { + bufferedCalls.forEach(serviceCall => { + if (!this.serviceRefs) { + return; + } + return serviceCall(this.serviceRefs); + }); + }); + } +} diff --git a/x-pack/plugins/secops/public/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/plugins/secops/public/lib/adapters/framework/testing_framework_adapter.ts new file mode 100644 index 0000000000000..c829e1d26e6a4 --- /dev/null +++ b/x-pack/plugins/secops/public/lib/adapters/framework/testing_framework_adapter.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AppFrameworkAdapter } from '../../lib'; + +export class AppTestingFrameworkAdapter implements AppFrameworkAdapter { + public appState?: object; + public dateFormat?: string; + public kbnVersion?: string; + public scaledDateFormat?: string; + public timezone?: string; + + constructor() { + this.appState = {}; + } + + public render() { + return; + } + public renderBreadcrumbs() { + return; + } + public setUISettings() { + return; + } +} diff --git a/x-pack/plugins/secops/public/lib/adapters/observable_api/kibana_observable_api.ts b/x-pack/plugins/secops/public/lib/adapters/observable_api/kibana_observable_api.ts new file mode 100644 index 0000000000000..5a8a7c3a4e89e --- /dev/null +++ b/x-pack/plugins/secops/public/lib/adapters/observable_api/kibana_observable_api.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ajax } from 'rxjs/ajax'; +import { map } from 'rxjs/operators'; + +import { AppObservableApi, AppObservableApiPostParams, AppObservableApiResponse } from '../../lib'; + +export class AppKibanaObservableApiAdapter implements AppObservableApi { + private basePath: string; + private defaultHeaders: { + [headerName: string]: string; + }; + + constructor({ basePath, xsrfToken }: { basePath: string; xsrfToken: string }) { + this.basePath = basePath; + this.defaultHeaders = { + 'kbn-version': xsrfToken, + }; + } + + public post = ({ + url, + body, + }: AppObservableApiPostParams): AppObservableApiResponse => + ajax({ + body: body ? JSON.stringify(body) : undefined, + headers: { + ...this.defaultHeaders, + 'Content-Type': 'application/json', + }, + method: 'POST', + responseType: 'json', + timeout: 30000, + url: `${this.basePath}/api/${url}`, + withCredentials: true, + }).pipe(map(({ response, status }) => ({ response, status }))); +} diff --git a/x-pack/plugins/secops/public/lib/compose/kibana_compose.ts b/x-pack/plugins/secops/public/lib/compose/kibana_compose.ts new file mode 100644 index 0000000000000..aca5dfa6e119e --- /dev/null +++ b/x-pack/plugins/secops/public/lib/compose/kibana_compose.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import 'ui/autoload/all'; +// @ts-ignore: path dynamic for kibana +import chrome from 'ui/chrome'; +// @ts-ignore: path dynamic for kibana +import { uiModules } from 'ui/modules'; +import uiRoutes from 'ui/routes'; +// @ts-ignore: path dynamic for kibana +import { timezoneProvider } from 'ui/vis/lib/timezone'; + +import { AppKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api'; + +import introspectionQueryResultData from '../../../common/graphql/introspection.json'; +import { AppKibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { AppFrontendLibs } from '../lib'; + +import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; +import ApolloClient from 'apollo-client'; +import { ApolloLink } from 'apollo-link'; +import { HttpLink } from 'apollo-link-http'; +import { withClientState } from 'apollo-link-state'; + +export function compose(): AppFrontendLibs { + const cache = new InMemoryCache({ + fragmentMatcher: new IntrospectionFragmentMatcher({ + introspectionQueryResultData, + }), + }); + + const observableApi = new AppKibanaObservableApiAdapter({ + basePath: chrome.getBasePath(), + xsrfToken: chrome.getXsrfToken(), + }); + + const graphQLOptions = { + cache, + link: ApolloLink.from([ + withClientState({ + cache, + resolvers: {}, + }), + new HttpLink({ + credentials: 'same-origin', + headers: { + 'kbn-xsrf': chrome.getXsrfToken(), + }, + uri: `${chrome.getBasePath()}/api/secops/graphql`, + }), + ]), + }; + + const apolloClient = new ApolloClient(graphQLOptions); + + const infraModule = uiModules.get('app/secops'); + + const framework = new AppKibanaFrameworkAdapter(infraModule, uiRoutes, timezoneProvider); + + const libs: AppFrontendLibs = { + apolloClient, + framework, + observableApi, + }; + return libs; +} diff --git a/x-pack/plugins/secops/public/lib/compose/testing_compose.ts b/x-pack/plugins/secops/public/lib/compose/testing_compose.ts new file mode 100644 index 0000000000000..c6f2fb5b07f4c --- /dev/null +++ b/x-pack/plugins/secops/public/lib/compose/testing_compose.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import 'ui/autoload/all'; +// @ts-ignore: path dynamic for kibana +import chrome from 'ui/chrome'; +// @ts-ignore: path dynamic for kibana +import { uiModules } from 'ui/modules'; +import uiRoutes from 'ui/routes'; +// @ts-ignore: path dynamic for kibana +import { timezoneProvider } from 'ui/vis/lib/timezone'; + +import { InMemoryCache } from 'apollo-cache-inmemory'; +import ApolloClient from 'apollo-client'; +import { SchemaLink } from 'apollo-link-schema'; +import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools'; +import { AppKibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { AppKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api'; +import { AppFrontendLibs } from '../lib'; + +export function compose(): AppFrontendLibs { + const infraModule = uiModules.get('app/infa'); + const observableApi = new AppKibanaObservableApiAdapter({ + basePath: chrome.getBasePath(), + xsrfToken: chrome.getXsrfToken(), + }); + const framework = new AppKibanaFrameworkAdapter(infraModule, uiRoutes, timezoneProvider); + const typeDefs = ` + Query {} +`; + + const mocks = { + Mutation: () => undefined, + Query: () => undefined, + }; + + const schema = makeExecutableSchema({ typeDefs }); + addMockFunctionsToSchema({ + mocks, + schema, + }); + + const cache = new InMemoryCache((window as any).__APOLLO_CLIENT__); + + const apolloClient = new ApolloClient({ + cache, + link: new SchemaLink({ schema }), + }); + + const libs: AppFrontendLibs = { + apolloClient, + framework, + observableApi, + }; + return libs; +} diff --git a/x-pack/plugins/secops/public/lib/lib.ts b/x-pack/plugins/secops/public/lib/lib.ts new file mode 100644 index 0000000000000..e9ec1258c538a --- /dev/null +++ b/x-pack/plugins/secops/public/lib/lib.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IModule, IScope } from 'angular'; +import { NormalizedCacheObject } from 'apollo-cache-inmemory'; +import ApolloClient from 'apollo-client'; +import React from 'react'; +import { Observable } from 'rxjs'; + +export interface AppFrontendLibs { + framework: AppFrameworkAdapter; + apolloClient: AppApolloClient; + observableApi: AppObservableApi; +} + +export type AppTimezoneProvider = () => string; + +export type AppApolloClient = ApolloClient; + +export interface AppFrameworkAdapter { + // Insstance vars + appState?: object; + dateFormat?: string; + kbnVersion?: string; + scaledDateFormat?: string; + timezone?: string; + + // Methods + setUISettings(key: string, value: any): void; + render(component: React.ReactElement): void; + renderBreadcrumbs(component: React.ReactElement): void; +} + +export interface AppFramworkAdapterConstructable { + new (uiModule: IModule, timezoneProvider: AppTimezoneProvider): AppFrameworkAdapter; +} + +export interface AppObservableApiPostParams { + url: string; + body?: RequestBody; +} + +export type AppObservableApiResponse = Observable<{ + status: number; + response: BodyType; +}>; + +export interface AppObservableApi { + post( + params: AppObservableApiPostParams + ): AppObservableApiResponse; +} + +export interface AppUiKibanaAdapterScope extends IScope { + breadcrumbs: any[]; + topNavMenu: any[]; +} + +export interface AppKibanaUIConfig { + get(key: string): any; + set(key: string, value: any): Promise; +} + +export interface AppKibanaAdapterServiceRefs { + config: AppKibanaUIConfig; + rootScope: IScope; +} + +export type AppBufferedKibanaServiceCall = (serviceRefs: ServiceRefs) => void; diff --git a/x-pack/plugins/secops/public/pages/404.tsx b/x-pack/plugins/secops/public/pages/404.tsx new file mode 100644 index 0000000000000..a025f3ace88b3 --- /dev/null +++ b/x-pack/plugins/secops/public/pages/404.tsx @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { pure } from 'recompose'; + +export const NotFoundPage = pure(() =>
No content found
); diff --git a/x-pack/plugins/secops/public/pages/home/index.tsx b/x-pack/plugins/secops/public/pages/home/index.tsx index edcccfe7a0e22..9d646390b9c69 100644 --- a/x-pack/plugins/secops/public/pages/home/index.tsx +++ b/x-pack/plugins/secops/public/pages/home/index.tsx @@ -4,15 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import * as React from 'react'; import { pure } from 'recompose'; import { ColumnarPage } from '../../components/page'; -const Home: React.SFC = () => ( +export const HomePage = pure(() => (

Hello Sec Ops

-); - -export const HomePage = pure(Home); +)); diff --git a/x-pack/plugins/secops/public/register_feature.ts b/x-pack/plugins/secops/public/register_feature.ts index 322ab435eef4f..ede194a31a0f3 100644 --- a/x-pack/plugins/secops/public/register_feature.ts +++ b/x-pack/plugins/secops/public/register_feature.ts @@ -9,14 +9,14 @@ import { FeatureCatalogueRegistryProvider, } from 'ui/registry/feature_catalogue'; -const APP_ID = 'infra'; +const APP_ID = 'secops'; FeatureCatalogueRegistryProvider.register(() => ({ id: 'secops', title: 'Sec Ops', description: 'Explore security metrics and logs for events and alerts', icon: 'securityApp', - path: `/app/${APP_ID}`, + path: `/app/${APP_ID}#home`, showOnHomePage: true, category: FeatureCatalogueCategory.DATA, })); diff --git a/x-pack/plugins/secops/public/routes.tsx b/x-pack/plugins/secops/public/routes.tsx index 8990ee5cd9f2c..908d45af3c0ff 100644 --- a/x-pack/plugins/secops/public/routes.tsx +++ b/x-pack/plugins/secops/public/routes.tsx @@ -16,13 +16,12 @@ interface RouterProps { history: History; } -const Routes: React.SFC = ({ history }) => ( +export const PageRouter = pure(({ history }) => ( - + + -); - -export PageRouter = pure(Routes); +)); diff --git a/x-pack/plugins/secops/server/init_server.ts b/x-pack/plugins/secops/server/init_server.ts index cfacf9f029dbc..98107857289d4 100644 --- a/x-pack/plugins/secops/server/init_server.ts +++ b/x-pack/plugins/secops/server/init_server.ts @@ -7,9 +7,9 @@ import { IResolvers, makeExecutableSchema } from 'graphql-tools'; import { schemas } from './graphql'; import { createSourcesResolvers } from './graphql/sources'; -import { BackendLibs } from './lib/types'; +import { AppBackendLibs } from './lib/types'; -export const initServer = (libs: BackendLibs) => { +export const initServer = (libs: AppBackendLibs) => { const schema = makeExecutableSchema({ resolvers: [createSourcesResolvers(libs) as IResolvers], typeDefs: schemas, diff --git a/x-pack/plugins/secops/server/lib/compose/kibana.ts b/x-pack/plugins/secops/server/lib/compose/kibana.ts index 23c5c3a45bd23..6d9be1afd4dc2 100644 --- a/x-pack/plugins/secops/server/lib/compose/kibana.ts +++ b/x-pack/plugins/secops/server/lib/compose/kibana.ts @@ -6,51 +6,25 @@ import { Server } from 'hapi'; -import { ElasticsearchCapabilitiesAdapter } from '../adapters/capabilities/elasticsearch_capabilities_adapter'; -import { InfraKibanaConfigurationAdapter } from '../adapters/configuration/kibana_configuration_adapter'; -import { FrameworkFieldsAdapter } from '../adapters/fields/framework_fields_adapter'; -import { InfraKibanaBackendFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; -import { InfraKibanaLogEntriesAdapter } from '../adapters/log_entries/kibana_log_entries_adapter'; -import { KibanaMetricsAdapter } from '../adapters/metrics/kibana_metrics_adapter'; -import { ElasticsearchNodesAdapter } from '../adapters/nodes/elasticsearch_nodes_adapter'; -import { InfraElasticsearchSourceStatusAdapter } from '../adapters/source_status'; -import { InfraConfigurationSourcesAdapter } from '../adapters/sources/configuration_sources_adapter'; -import { InfraCapabilitiesDomain } from '../domains/capabilities_domain'; -import { InfraFieldsDomain } from '../domains/fields_domain'; -import { InfraLogEntriesDomain } from '../domains/log_entries_domain'; -import { InfraMetricsDomain } from '../domains/metrics_domain'; -import { InfraNodesDomain } from '../domains/nodes_domain'; -import { InfraBackendLibs, InfraConfiguration, InfraDomainLibs } from '../infra_types'; -import { InfraSourceStatus } from '../source_status'; -import { InfraSources } from '../sources'; +import { KibanaConfigurationAdapter } from '../configuration/kibana_configuration_adapter'; +import { KibanaBackendFrameworkAdapter } from '../framework/kibana_framework_adapter'; +import { Sources } from '../sources'; +import { ConfigurationSourcesAdapter } from '../sources/configuration_sources_adapter'; +import { AppBackendLibs, AppDomainLibs, Configuration } from '../types'; -export function compose(server: Server): InfraBackendLibs { - const configuration = new InfraKibanaConfigurationAdapter(server); - const framework = new InfraKibanaBackendFrameworkAdapter(server); - const sources = new InfraSources(new InfraConfigurationSourcesAdapter(configuration)); - const sourceStatus = new InfraSourceStatus(new InfraElasticsearchSourceStatusAdapter(framework), { - sources, - }); +export function compose(server: Server): AppBackendLibs { + const configuration = new KibanaConfigurationAdapter(server); + const framework = new KibanaBackendFrameworkAdapter(server); + const sources = new Sources(new ConfigurationSourcesAdapter(configuration)); - const domainLibs: InfraDomainLibs = { - capabilities: new InfraCapabilitiesDomain(new ElasticsearchCapabilitiesAdapter(framework), { - sources, - }), - fields: new InfraFieldsDomain(new FrameworkFieldsAdapter(framework), { - sources, - }), - logEntries: new InfraLogEntriesDomain(new InfraKibanaLogEntriesAdapter(framework), { - sources, - }), - nodes: new InfraNodesDomain(new ElasticsearchNodesAdapter(framework)), - metrics: new InfraMetricsDomain(new KibanaMetricsAdapter(framework)), + const domainLibs: AppDomainLibs = { + hello: '', }; - const libs: InfraBackendLibs = { + const libs: AppBackendLibs = { configuration, framework, sources, - sourceStatus, ...domainLibs, }; diff --git a/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts b/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts index 9277a9619fc4e..0050b7010b506 100644 --- a/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts +++ b/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts @@ -83,7 +83,7 @@ export const graphqlHapi: IRegister = Object.assign( }, { attributes: { - name: 'graphql', + name: 'graphql-secops', }, } ); @@ -123,7 +123,7 @@ export const graphiqlHapi: IRegister = Object.assign( }, { attributes: { - name: 'graphiql', + name: 'graphiql-secops', }, } ); From 885b80f17cc72f9c03b3ce9556663cb1863823c3 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 26 Oct 2018 09:49:20 -0400 Subject: [PATCH 3/9] fix type issue and hapi new version --- x-pack/package.json | 1 + x-pack/plugins/secops/package.json | 5 +- .../lib/framework/apollo_server_hapi.ts | 70 ++++++++----------- .../lib/framework/kibana_framework_adapter.ts | 22 ++++-- x-pack/plugins/secops/yarn.lock | 40 ++--------- x-pack/yarn.lock | 7 ++ 6 files changed, 60 insertions(+), 85 deletions(-) diff --git a/x-pack/package.json b/x-pack/package.json index 636cad0bc31a4..9f3ce898c2a9d 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -54,6 +54,7 @@ "@types/react-dom": "^16.0.5", "@types/react-redux": "^6.0.6", "@types/react-router-dom": "^4.3.1", + "@types/recompose": "^0.26.0", "@types/reduce-reducers": "^0.1.3", "@types/sinon": "^5.0.1", "@types/supertest": "^2.0.5", diff --git a/x-pack/plugins/secops/package.json b/x-pack/plugins/secops/package.json index bc99a3e702693..cb1cdba7da3fe 100644 --- a/x-pack/plugins/secops/package.json +++ b/x-pack/plugins/secops/package.json @@ -6,10 +6,9 @@ "build-graphql-types": "node scripts/generate_types_from_graphql.js" }, "devDependencies": { - "@types/boom": "3.2.2", + "@types/boom": "7.2.0", "@types/color": "^3.0.0", - "@types/lodash": "^4.14.110", - "@types/recompose": "^0.27.0" + "@types/lodash": "^4.14.110" }, "dependencies": { "boom": "3.1.1", diff --git a/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts b/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts index 0050b7010b506..0af06c488e26e 100644 --- a/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts +++ b/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts @@ -6,60 +6,59 @@ import * as GraphiQL from 'apollo-server-module-graphiql'; import Boom from 'boom'; -import { IReply, Request, Server } from 'hapi'; +import { Plugin, Request, ResponseToolkit, RouteOptions, Server } from 'hapi'; import { GraphQLOptions, runHttpQuery } from 'apollo-server-core'; -export interface IRegister { - (server: Server, options: any, next: () => void): void; - attributes: { - name: string; - version?: string; - }; -} - -export type HapiOptionsFunction = (req?: Request) => GraphQLOptions | Promise; +export type HapiOptionsFunction = (req: Request) => GraphQLOptions | Promise; -export interface HapiPluginOptions { +export interface HapiGraphQLPluginOptions { path: string; vhost?: string; - route?: any; + route?: RouteOptions; graphqlOptions: GraphQLOptions | HapiOptionsFunction; } -export const graphqlHapi: IRegister = Object.assign( - (server: Server, options: HapiPluginOptions, next: () => void) => { +export const graphqlHapi: Plugin = { + name: 'graphql-secops', + register: (server: Server, options: HapiGraphQLPluginOptions) => { if (!options || !options.graphqlOptions) { throw new Error('Apollo Server requires options.'); } server.route({ - config: options.route || {}, - handler: async (request: Request, reply: IReply) => { + options: options.route || {}, + handler: async (request: Request, h: ResponseToolkit) => { try { + const query = + request.method === 'post' + ? (request.payload as Record) + : (request.query as Record); + const gqlResponse = await runHttpQuery([request], { method: request.method.toUpperCase(), options: options.graphqlOptions, - query: request.method === 'post' ? request.payload : request.query, + query, }); - return reply(gqlResponse).type('application/json'); + return h.response(gqlResponse).type('application/json'); } catch (error) { if ('HttpQueryError' !== error.name) { - const queryError = Boom.wrap(error); + const queryError = Boom.boomify(error); queryError.output.payload.message = error.message; - return reply(queryError); + return queryError; } if (error.isGraphQLError === true) { - return reply(error.message) + return h + .response(error.message) .code(error.statusCode) .type('application/json'); } - const genericError = Boom.create(error.statusCode, error.message); + const genericError = new Boom(error.message, { statusCode: error.statusCode }); if (error.headers) { Object.keys(error.headers).forEach(header => { @@ -78,15 +77,8 @@ export const graphqlHapi: IRegister = Object.assign( path: options.path || '/graphql', vhost: options.vhost || undefined, }); - - return next(); }, - { - attributes: { - name: 'graphql-secops', - }, - } -); +}; export type HapiGraphiQLOptionsFunction = ( req?: Request @@ -100,30 +92,26 @@ export interface HapiGraphiQLPluginOptions { graphiqlOptions: GraphiQL.GraphiQLData | HapiGraphiQLOptionsFunction; } -export const graphiqlHapi: IRegister = Object.assign( - (server: Server, options: HapiGraphiQLPluginOptions) => { +export const graphiqlHapi: Plugin = { + name: 'graphiql-secops', + register: (server: Server, options: HapiGraphiQLPluginOptions) => { if (!options || !options.graphiqlOptions) { throw new Error('Apollo Server GraphiQL requires options.'); } server.route({ - config: options.route || {}, - handler: async (request: Request, reply: IReply) => { + options: options.route || {}, + handler: async (request: Request, h: ResponseToolkit) => { const graphiqlString = await GraphiQL.resolveGraphiQLString( request.query, options.graphiqlOptions, request ); - return reply(graphiqlString).type('text/html'); + return h.response(graphiqlString).type('text/html'); }, method: 'GET', path: options.path || '/graphiql', }); }, - { - attributes: { - name: 'graphiql-secops', - }, - } -); +}; diff --git a/x-pack/plugins/secops/server/lib/framework/kibana_framework_adapter.ts b/x-pack/plugins/secops/server/lib/framework/kibana_framework_adapter.ts index 17a8b8bc05925..3a35652198a1e 100644 --- a/x-pack/plugins/secops/server/lib/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/secops/server/lib/framework/kibana_framework_adapter.ts @@ -13,7 +13,19 @@ import { internalFrameworkRequest, WrappableRequest, } from './adapter_types'; -import { graphiqlHapi, graphqlHapi } from './apollo_server_hapi'; +import { + graphiqlHapi, + graphqlHapi, + HapiGraphiQLPluginOptions, + HapiGraphQLPluginOptions, +} from './apollo_server_hapi'; + +declare module 'hapi' { + interface PluginProperties { + elasticsearch: any; + kibana: any; + } +} export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { public version: string; @@ -37,7 +49,7 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { } public registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void { - this.server.register({ + this.server.register({ options: { graphqlOptions: (req: Request) => ({ context: { req: wrapRequest(req) }, @@ -45,10 +57,10 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { }), path: routePath, }, - register: graphqlHapi, + plugin: graphqlHapi, }); - this.server.register({ + this.server.register({ options: { graphiqlOptions: { endpointURL: routePath, @@ -56,7 +68,7 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { }, path: `${routePath}/graphiql`, }, - register: graphiqlHapi, + plugin: graphiqlHapi, }); } } diff --git a/x-pack/plugins/secops/yarn.lock b/x-pack/plugins/secops/yarn.lock index ce8b8a3df33f0..3680885de5648 100644 --- a/x-pack/plugins/secops/yarn.lock +++ b/x-pack/plugins/secops/yarn.lock @@ -2,12 +2,10 @@ # yarn lockfile v1 -"@types/boom@3.2.2": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@types/boom/-/boom-3.2.2.tgz#6773bb1bbfec111f5ea683874b8d932157d58885" - integrity sha512-Wbgg2JXCnlEMWB2faZgT8x1MPPgzqqLBWx1zXXWGPDQDo9CcvQkIW89QqY+yekoXIKI+qW/eZsg/gv6+WNFwEg== - dependencies: - "@types/node" "*" +"@types/boom@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@types/boom/-/boom-7.2.0.tgz#19c36cbb5811a7493f0f2e37f31d42b28df1abc1" + integrity sha512-HonbGsHFbskh9zRAzA6tabcw18mCOsSEOL2ibGAuVqk6e7nElcRmWO5L4UfIHpDbWBWw+eZYFdsQ1+MEGgpcVA== "@types/color-convert@*": version "1.9.0" @@ -33,31 +31,6 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.117.tgz#695a7f514182771a1e0f4345d189052ee33c8778" integrity sha512-xyf2m6tRbz8qQKcxYZa7PA4SllYcay+eh25DN3jmNYY6gSTL7Htc/bttVdkqj2wfJGbeWlQiX8pIyJpKU+tubw== -"@types/node@*": - version "10.12.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.0.tgz#ea6dcbddbc5b584c83f06c60e82736d8fbb0c235" - integrity sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ== - -"@types/prop-types@*": - version "15.5.6" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.6.tgz#9c03d3fed70a8d517c191b7734da2879b50ca26c" - integrity sha512-ZBFR7TROLVzCkswA3Fmqq+IIJt62/T7aY/Dmz+QkU7CaW2QFqAitCE8Ups7IzmGhcN1YWMBT4Qcoc07jU9hOJQ== - -"@types/react@*": - version "16.4.18" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.4.18.tgz#2e28a2e7f92d3fa7d6a65f2b73275c3e3138a13d" - integrity sha512-eFzJKEg6pdeaukVLVZ8Xb79CTl/ysX+ExmOfAAqcFlCCK5TgFDD9kWR0S18sglQ3EmM8U+80enjUqbfnUyqpdA== - dependencies: - "@types/prop-types" "*" - csstype "^2.2.0" - -"@types/recompose@^0.27.0": - version "0.27.0" - resolved "https://registry.yarnpkg.com/@types/recompose/-/recompose-0.27.0.tgz#66955b8ea4d477c3c03f09a571380ca82549cdac" - integrity sha512-p92Unulom3aSdgWCFXrKIpd6obaO3gOKJhEBJUBohlcC21aDXnsnu1BbYL42GeV5qcXezeRXM1SXiyKpnFSGlA== - dependencies: - "@types/react" "*" - boom@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/boom/-/boom-3.1.1.tgz#b6424f01ed8d492b2b12ae86047c24e8b6a7c937" @@ -65,11 +38,6 @@ boom@3.1.1: dependencies: hoek "3.x.x" -csstype@^2.2.0: - version "2.5.7" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.7.tgz#bf9235d5872141eccfb2d16d82993c6b149179ff" - integrity sha512-Nt5VDyOTIIV4/nRFswoCKps1R5CD1hkiyjBE9/thNaNZILLEviVw9yWQw15+O+CpNjQKB/uvdcxFFOrSflY3Yw== - hoek@3.x.x: version "3.0.4" resolved "https://registry.yarnpkg.com/hoek/-/hoek-3.0.4.tgz#268adff66bb6695c69b4789a88b1e0847c3f3123" diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 75f308e00caa0..e1a801a0c06b1 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -455,6 +455,13 @@ dependencies: csstype "^2.2.0" +"@types/recompose@^0.26.0": + version "0.26.5" + resolved "https://registry.yarnpkg.com/@types/recompose/-/recompose-0.26.5.tgz#8496b63c535a60c3584b8b0aca54bfb86679ed70" + integrity sha512-Il5stz/Z3pVIMl48pyggl6nnhRLQ8N8YN8hi0Anm0M5UjVh2uMSY0ah2vzwZZKxnca4NzyJArloSjsJ9fL2vWw== + dependencies: + "@types/react" "*" + "@types/reduce-reducers@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@types/reduce-reducers/-/reduce-reducers-0.1.3.tgz#69f252207622ced7e063c7526ad46ec60b69f0c0" From f4937fcc2d57999251ee533ce3bb742bee4e1f46 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 26 Oct 2018 10:24:09 -0400 Subject: [PATCH 4/9] remove route home, not needed for now --- x-pack/plugins/secops/index.ts | 4 ++-- x-pack/plugins/secops/public/register_feature.ts | 2 +- x-pack/plugins/secops/public/routes.tsx | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/secops/index.ts b/x-pack/plugins/secops/index.ts index e64cdb2ad80d7..10779b6db54be 100644 --- a/x-pack/plugins/secops/index.ts +++ b/x-pack/plugins/secops/index.ts @@ -24,7 +24,7 @@ export function secops(kibana: any) { euiIconType: 'securityApp', title: 'Sec Ops', listed: false, - url: `/app/${APP_ID}#/home`, + url: `/app/${APP_ID}`, }, home: ['plugins/secops/register_feature'], links: [ @@ -34,7 +34,7 @@ export function secops(kibana: any) { id: 'secops', order: 9000, title: 'Sec Ops', - url: `/app/${APP_ID}#/home`, + url: `/app/${APP_ID}`, }, ], }, diff --git a/x-pack/plugins/secops/public/register_feature.ts b/x-pack/plugins/secops/public/register_feature.ts index ede194a31a0f3..29357a6167e04 100644 --- a/x-pack/plugins/secops/public/register_feature.ts +++ b/x-pack/plugins/secops/public/register_feature.ts @@ -16,7 +16,7 @@ FeatureCatalogueRegistryProvider.register(() => ({ title: 'Sec Ops', description: 'Explore security metrics and logs for events and alerts', icon: 'securityApp', - path: `/app/${APP_ID}#home`, + path: `/app/${APP_ID}`, showOnHomePage: true, category: FeatureCatalogueCategory.DATA, })); diff --git a/x-pack/plugins/secops/public/routes.tsx b/x-pack/plugins/secops/public/routes.tsx index 908d45af3c0ff..ad16e1b4d6176 100644 --- a/x-pack/plugins/secops/public/routes.tsx +++ b/x-pack/plugins/secops/public/routes.tsx @@ -6,7 +6,7 @@ import { History } from 'history'; import React from 'react'; -import { Redirect, Route, Router, Switch } from 'react-router-dom'; +import { Route, Router, Switch } from 'react-router-dom'; import { pure } from 'recompose'; import { NotFoundPage } from './pages/404'; @@ -19,8 +19,7 @@ interface RouterProps { export const PageRouter = pure(({ history }) => ( - - + From 9482792322e172b3a4ffc4169f27d4dc315a02c0 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 26 Oct 2018 13:56:57 -0400 Subject: [PATCH 5/9] Add configuration + delete noise --- x-pack/package.json | 6 +- .../secops/common/graphql/introspection.json | 198 +++++-- x-pack/plugins/secops/common/graphql/types.ts | 140 ++++- .../server/graphql/sources/schema.gql.ts | 34 +- .../secops/server/lib/sources/index.ts | 2 - x-pack/yarn.lock | 511 +++++++++++++----- 6 files changed, 682 insertions(+), 209 deletions(-) diff --git a/x-pack/package.json b/x-pack/package.json index 9f3ce898c2a9d..30224ee04204e 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -83,9 +83,9 @@ "expect.js": "0.3.1", "fancy-log": "^1.3.2", "fetch-mock": "^5.13.1", - "graphql-code-generator": "^0.10.1", - "graphql-codegen-introspection-template": "^0.10.5", - "graphql-codegen-typescript-template": "^0.10.1", + "graphql-code-generator": "^0.12.6", + "graphql-codegen-introspection-template": "^0.12.6", + "graphql-codegen-typescript-template": "^0.12.6", "gulp": "3.9.1", "gulp-mocha": "^6.0.0", "gulp-multi-process": "^1.3.1", diff --git a/x-pack/plugins/secops/common/graphql/introspection.json b/x-pack/plugins/secops/common/graphql/introspection.json index 55fa825f2f240..702f9eee484cd 100644 --- a/x-pack/plugins/secops/common/graphql/introspection.json +++ b/x-pack/plugins/secops/common/graphql/introspection.json @@ -61,8 +61,7 @@ { "kind": "SCALAR", "name": "ID", - "description": - "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", + "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", "fields": null, "inputFields": null, "interfaces": null, @@ -85,6 +84,132 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "configuration", + "description": "The raw configuration of the source", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "SourceConfiguration", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SourceConfiguration", + "description": "A set of configuration options for an infrastructure data source", + "fields": [ + { + "name": "fields", + "description": "The field mapping to use for this source", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "SourceFields", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SourceFields", + "description": "A mapping of semantic fields to their document counterparts", + "fields": [ + { + "name": "container", + "description": "The field to identify a container by", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "host", + "description": "The fields to identify a host by", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "message", + "description": "The fields that may contain the log event message. The first field found win.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pod", + "description": "The field to identify a pod by", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "tiebreaker", + "description": "The field to use as a tiebreaker for log events that have identical timestamps", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "timestamp", + "description": "The field to use as a timestamp for metrics and logs", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -92,11 +217,20 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "SCALAR", + "name": "String", + "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "__Schema", - "description": - "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", "fields": [ { "name": "types", @@ -132,8 +266,7 @@ }, { "name": "mutationType", - "description": - "If this server supports mutation, the type that mutation operations will be rooted at.", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", "args": [], "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, "isDeprecated": false, @@ -141,8 +274,7 @@ }, { "name": "subscriptionType", - "description": - "If this server support subscription, the type that subscription operations will be rooted at.", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", "args": [], "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, "isDeprecated": false, @@ -177,8 +309,7 @@ { "kind": "OBJECT", "name": "__Type", - "description": - "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", "fields": [ { "name": "kind", @@ -332,15 +463,13 @@ }, { "name": "OBJECT", - "description": - "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", "isDeprecated": false, "deprecationReason": null }, { "name": "INTERFACE", - "description": - "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", "isDeprecated": false, "deprecationReason": null }, @@ -358,8 +487,7 @@ }, { "name": "INPUT_OBJECT", - "description": - "Indicates this type is an input object. `inputFields` is a valid field.", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", "isDeprecated": false, "deprecationReason": null }, @@ -378,17 +506,6 @@ ], "possibleTypes": null }, - { - "kind": "SCALAR", - "name": "String", - "description": - "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, { "kind": "SCALAR", "name": "Boolean", @@ -402,8 +519,7 @@ { "kind": "OBJECT", "name": "__Field", - "description": - "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", "fields": [ { "name": "name", @@ -486,8 +602,7 @@ { "kind": "OBJECT", "name": "__InputValue", - "description": - "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", "fields": [ { "name": "name", @@ -523,8 +638,7 @@ }, { "name": "defaultValue", - "description": - "A GraphQL-formatted string representing the default value for this input value.", + "description": "A GraphQL-formatted string representing the default value for this input value.", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, @@ -539,8 +653,7 @@ { "kind": "OBJECT", "name": "__EnumValue", - "description": - "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", "fields": [ { "name": "name", @@ -591,8 +704,7 @@ { "kind": "OBJECT", "name": "__Directive", - "description": - "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", "fields": [ { "name": "name", @@ -699,8 +811,7 @@ { "kind": "ENUM", "name": "__DirectiveLocation", - "description": - "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", "fields": null, "inputFields": null, "interfaces": null, @@ -820,8 +931,7 @@ "directives": [ { "name": "skip", - "description": - "Directs the executor to skip this field or fragment when the `if` argument is true.", + "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], "args": [ { @@ -838,8 +948,7 @@ }, { "name": "include", - "description": - "Directs the executor to include this field or fragment only when the `if` argument is true.", + "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], "args": [ { @@ -861,8 +970,7 @@ "args": [ { "name": "reason", - "description": - "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", + "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "defaultValue": "\"No longer supported\"" } diff --git a/x-pack/plugins/secops/common/graphql/types.ts b/x-pack/plugins/secops/common/graphql/types.ts index feb424adb2367..36eb79acf0dd8 100644 --- a/x-pack/plugins/secops/common/graphql/types.ts +++ b/x-pack/plugins/secops/common/graphql/types.ts @@ -1,13 +1,28 @@ /* tslint:disable */ import { GraphQLResolveInfo } from 'graphql'; -type Resolver = ( - parent: any, +export type Resolver = ( + parent: Parent, args: Args, - context: any, + context: Context, info: GraphQLResolveInfo ) => Promise | Result; +export type SubscriptionResolver = { + subscribe( + parent: P, + args: Args, + context: Context, + info: GraphQLResolveInfo + ): AsyncIterator; + resolve?( + parent: P, + args: Args, + context: Context, + info: GraphQLResolveInfo + ): R | Result | Promise; +}; + export interface Query { source: Source /** Get an infrastructure data source by id */; allSources: Source[] /** Get a list of all infrastructure data sources */; @@ -15,29 +30,128 @@ export interface Query { export interface Source { id: string /** The id of the source */; + configuration: SourceConfiguration /** The raw configuration of the source */; +} +/** A set of configuration options for an infrastructure data source */ +export interface SourceConfiguration { + fields: SourceFields /** The field mapping to use for this source */; +} +/** A mapping of semantic fields to their document counterparts */ +export interface SourceFields { + container: string /** The field to identify a container by */; + host: string /** The fields to identify a host by */; + message: string[] /** The fields that may contain the log event message. The first field found win. */; + pod: string /** The field to identify a pod by */; + tiebreaker: string /** The field to use as a tiebreaker for log events that have identical timestamps */; + timestamp: string /** The field to use as a timestamp for metrics and logs */; +} +export interface SourceQueryArgs { + id: string /** The id of the source */; } export namespace QueryResolvers { - export interface Resolvers { - source?: SourceResolver /** Get an infrastructure data source by id */; - allSources?: AllSourcesResolver /** Get a list of all infrastructure data sources */; + export interface Resolvers { + source?: SourceResolver /** Get an infrastructure data source by id */; + allSources?: AllSourcesResolver< + Source[], + any, + Context + > /** Get a list of all infrastructure data sources */; } - export type SourceResolver = Resolver; + export type SourceResolver = Resolver< + R, + Parent, + Context, + SourceArgs + >; export interface SourceArgs { id: string /** The id of the source */; } - export type AllSourcesResolver = Resolver; + export type AllSourcesResolver = Resolver< + R, + Parent, + Context + >; } export namespace SourceResolvers { - export interface Resolvers { - id?: IdResolver /** The id of the source */; + export interface Resolvers { + id?: IdResolver /** The id of the source */; + configuration?: ConfigurationResolver< + SourceConfiguration, + any, + Context + > /** The raw configuration of the source */; } - export type IdResolver = Resolver; + export type IdResolver = Resolver; + export type ConfigurationResolver< + R = SourceConfiguration, + Parent = any, + Context = any + > = Resolver; } -export interface SourceQueryArgs { - id: string /** The id of the source */; +/** A set of configuration options for an infrastructure data source */ +export namespace SourceConfigurationResolvers { + export interface Resolvers { + fields?: FieldsResolver< + SourceFields, + any, + Context + > /** The field mapping to use for this source */; + } + + export type FieldsResolver = Resolver< + R, + Parent, + Context + >; +} +/** A mapping of semantic fields to their document counterparts */ +export namespace SourceFieldsResolvers { + export interface Resolvers { + container?: ContainerResolver /** The field to identify a container by */; + host?: HostResolver /** The fields to identify a host by */; + message?: MessageResolver< + string[], + any, + Context + > /** The fields that may contain the log event message. The first field found win. */; + pod?: PodResolver /** The field to identify a pod by */; + tiebreaker?: TiebreakerResolver< + string, + any, + Context + > /** The field to use as a tiebreaker for log events that have identical timestamps */; + timestamp?: TimestampResolver< + string, + any, + Context + > /** The field to use as a timestamp for metrics and logs */; + } + + export type ContainerResolver = Resolver< + R, + Parent, + Context + >; + export type HostResolver = Resolver; + export type MessageResolver = Resolver< + R, + Parent, + Context + >; + export type PodResolver = Resolver; + export type TiebreakerResolver = Resolver< + R, + Parent, + Context + >; + export type TimestampResolver = Resolver< + R, + Parent, + Context + >; } diff --git a/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts b/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts index d2372362e4594..2060c71a8e499 100644 --- a/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts +++ b/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts @@ -7,15 +7,39 @@ import gql from 'graphql-tag'; export const sourcesSchema = gql` - type Source { - "The id of the source" - id: ID! - } - extend type Query { "Get an infrastructure data source by id" source("The id of the source" id: ID!): Source! "Get a list of all infrastructure data sources" allSources: [Source!]! } + + type Source { + "The id of the source" + id: ID! + "The raw configuration of the source" + configuration: SourceConfiguration! + } + + "A set of configuration options for an infrastructure data source" + type SourceConfiguration { + "The field mapping to use for this source" + fields: SourceFields! + } + + "A mapping of semantic fields to their document counterparts" + type SourceFields { + "The field to identify a container by" + container: String! + "The fields to identify a host by" + host: String! + "The fields that may contain the log event message. The first field found win." + message: [String!]! + "The field to identify a pod by" + pod: String! + "The field to use as a tiebreaker for log events that have identical timestamps" + tiebreaker: String! + "The field to use as a timestamp for metrics and logs" + timestamp: String! + } `; diff --git a/x-pack/plugins/secops/server/lib/sources/index.ts b/x-pack/plugins/secops/server/lib/sources/index.ts index b49ee5ef5cf87..d49451dde5de3 100644 --- a/x-pack/plugins/secops/server/lib/sources/index.ts +++ b/x-pack/plugins/secops/server/lib/sources/index.ts @@ -34,8 +34,6 @@ export interface SourceConfigurations { } export interface SourceConfiguration { - metricAlias: string; - logAlias: string; fields: { container: string; host: string; diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index e1a801a0c06b1..151f5f9baf1a3 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -67,6 +67,24 @@ resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.3.2.tgz#06c9ef22f18dd8c2b39ffe353868d4d0c13ea4f9" integrity sha1-BsnvIvGN2MKzn/41OGjU0ME+pPk= +"@graphql-modules/epoxy@0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@graphql-modules/epoxy/-/epoxy-0.1.6.tgz#f0555d72f5477f5a65bf880397137f422fb5bda5" + integrity sha512-DYq1RfTXEEaTYS2cKCTPNCdr6uCj3JgrERQjUMpn0bSBM28V2ncnx5ycDL+w2p/gnYiFD5LfHSJNnQ5u8/79Zg== + dependencies: + "@graphql-modules/logger" "0.1.6" + "@types/deepmerge" "2.1.0" + deepmerge "2.1.1" + graphql-tools "3.1.1" + +"@graphql-modules/logger@0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@graphql-modules/logger/-/logger-0.1.6.tgz#89da70baf9e5802f5dc970a501f500bdbd8efedf" + integrity sha512-gh2etdA4kHHXKiHQMhqub9eVBhr8rfi7Pe6/TLx/9Dmm7Ikqy0FvyDvg1zSstP5dRgGj5xm7z1EP8wxSxWHg2w== + dependencies: + moment "2.22.2" + winston "3.0.0" + "@kbn/datemath@link:../packages/kbn-datemath": version "0.0.0" uid "" @@ -231,6 +249,11 @@ resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-1.0.8.tgz#6c083127b330b3c2fc65cd0f3a6e9cbd9607b28c" integrity sha512-/UCphyyw97YAq4zKsuXH33R3UNB4jDSza0fLvMubWr/ONh9IePi1NbgFP222blhiCe724ebJs8U87+aDuAq/jA== +"@types/deepmerge@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/deepmerge/-/deepmerge-2.1.0.tgz#22f175e5cb55874fe818caa6fd50a1d98fc3d748" + integrity sha512-/0Ct/q5g+SgaACZ+A0ylY3071nEBN7QDnTWiCtaB3fx24UpoAQXf25yNVloOYVUis7jytM1F1WC78+EOwXkQJQ== + "@types/delay@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/delay/-/delay-2.0.1.tgz#61bcf318a74b61e79d1658fbf054f984c90ef901" @@ -275,10 +298,10 @@ resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.13.1.tgz#7d39750355c9ecb921816d6f76c080405b5f6bea" integrity sha512-a6vRcP4M6+7Lqev1JeF3hGFmC3FNBIJ5cRnaSN/z1LtGVr/CaY6nqSlQbnFr2j8ucDlUAQU43LCnJrz6uLUyHg== -"@types/handlebars@4.0.38": - version "4.0.38" - resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.38.tgz#d0ebec1934c0bba97f99a0cb703fe2ef8c4a662f" - integrity sha512-oMzU0D7jDp+H2go/i0XqBHfr+HEhYD/e1TvkhHi3yrhQm/7JFR8FJMdvoH76X8G1FBpgc6Pwi+QslCJBeJ1N9g== +"@types/handlebars@4.0.39": + version "4.0.39" + resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.39.tgz#961fb54db68030890942e6aeffe9f93a957807bd" + integrity sha512-vjaS7Q0dVqFp85QhyPSZqDKnTTCemcSHNHFvDdalO1s0Ifz5KuE64jQD5xoUkfdWwF4WpqdJEl7LsWH8rzhKJA== "@types/hapi@^17.0.18": version "17.0.18" @@ -311,6 +334,11 @@ dependencies: "@types/node" "*" +"@types/is-glob@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/is-glob/-/is-glob-4.0.0.tgz#fb8a2bff539025d4dcd6d5efe7689e03341b876d" + integrity sha512-zC/2EmD8scdsGIeE+Xg7kP7oi9VP90zgMQtm9Cr25av4V+a+k8slQyiT60qSw8KORYrOKlPXfHwoa1bQbRzskQ== + "@types/is-stream@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" @@ -396,10 +424,10 @@ resolved "https://registry.yarnpkg.com/@types/podium/-/podium-1.0.0.tgz#bfaa2151be2b1d6109cc69f7faa9dac2cba3bb20" integrity sha1-v6ohUb4rHWEJzGn3+qnawsujuyA= -"@types/prettier@1.13.1": - version "1.13.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.13.1.tgz#5dd359398de96863a0629156f382913a06ebe013" - integrity sha512-OlcCdqLtMvl+Hq4UkAxxppKX252NXsBm6RyJZVuBZtkduu3Dl8pdx78XS4K7oPGPOxpD6T+KzK0DV11G8ykTkw== +"@types/prettier@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.13.2.tgz#ffe96278e712a8d4e467e367a338b05e22872646" + integrity sha512-k6MCN8WuDiCj6O+UJsVMbrreZxkbrhQbO02oDj6yuRu8UAkp0MDdEcDKif8/gBKuJbT84kkO+VHQAqXkumEklg== "@types/prop-types@^15.5.3": version "15.5.3" @@ -873,6 +901,14 @@ apollo-link@^1.0.0, apollo-link@^1.2.2: apollo-utilities "^1.0.0" zen-observable-ts "^0.8.9" +apollo-link@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.3.tgz#9bd8d5fe1d88d31dc91dae9ecc22474d451fb70d" + integrity sha512-iL9yS2OfxYhigme5bpTbmRyC+Htt6tyo2fRMHT3K1XRL/C5IQDDz37OjpPy4ndx7WInSvfSZaaOTKFja9VWqSw== + dependencies: + apollo-utilities "^1.0.0" + zen-observable-ts "^0.8.10" + apollo-server-core@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-1.3.6.tgz#08636243c2de56fa8c267d68dd602cb1fbd323e3" @@ -1193,18 +1229,13 @@ async@^2.1.4: dependencies: lodash "^4.14.0" -async@^2.5.0: +async@^2.5.0, async@^2.6.0: version "2.6.1" resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== dependencies: lodash "^4.17.10" -async@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9" - integrity sha1-+PwEyjoTeErenhZBr5hXjPvWR6k= - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2498,31 +2529,72 @@ color-convert@^1.9.0: dependencies: color-name "^1.1.1" -color-name@^1.1.1: +color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3, color-name@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" + integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== +color@3.0.x: + version "3.0.0" + resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a" + integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +colornames@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" + integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y= + colors@0.5.x: version "0.5.1" resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774" integrity sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q= -colors@1.0.x: - version "1.0.3" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" - integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= +colors@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.2.tgz#2df8ff573dfbf255af562f8ce7181d6b971a359b" + integrity sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ== colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM= +colorspace@1.1.x: + version "1.1.1" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.1.tgz#9ac2491e1bc6f8fb690e2176814f8d091636d972" + integrity sha512-pI3btWyiuz7Ken0BWh9Elzsmv2bM9AhA7psXib4anUXy/orfZ/E0MbQwhSOG/9L8hLlalqrU0UhOuqxW1YjmVw== + dependencies: + color "3.0.x" + text-hex "1.0.x" + combined-stream@1.0.6, combined-stream@~1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" @@ -2552,10 +2624,10 @@ commander@2.15.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== -commander@2.16.0: - version "2.16.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50" - integrity sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew== +commander@2.18.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" + integrity sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ== commander@~2.17.1: version "2.17.1" @@ -2878,11 +2950,6 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" -cycle@1.0.x: - version "1.0.3" - resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" - integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI= - cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" @@ -3109,6 +3176,11 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deepmerge@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.1.1.tgz#e862b4e45ea0555072bf51e7fd0d9845170ae768" + integrity sha512-urQxA1smbLZ2cBbXbaYObM1dJ82aJ2H57A1C/Kklfh/ZN1bgH4G/n5KWhdNfOK11W98gqZfyYj7W4frJJRwA2w== + default-require-extensions@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" @@ -3231,6 +3303,15 @@ dfa@^1.0.0: dependencies: babel-runtime "^6.11.6" +diagnostics@^1.0.1, diagnostics@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a" + integrity sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ== + dependencies: + colorspace "1.1.x" + enabled "1.0.x" + kuler "1.0.x" + diff@3.5.0, diff@^3.1.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -3399,6 +3480,13 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= +enabled@1.0.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93" + integrity sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M= + dependencies: + env-variable "0.0.x" + encoding@^0.1.11: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" @@ -3467,6 +3555,11 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= +env-variable@0.0.x: + version "0.0.5" + resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.5.tgz#913dd830bef11e96a039c038d4130604eba37f88" + integrity sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA== + enzyme-adapter-react-16@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz#a8f4278b47e082fbca14f5bfb1ee50ee650717b4" @@ -3859,11 +3952,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -eyes@0.1.x: - version "0.1.8" - resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" - integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A= - falafel@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.1.0.tgz#96bb17761daba94f46d001738b3cedf3a67fe06c" @@ -3903,7 +3991,12 @@ fast-levenshtein@~2.0.4: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fb-watchman@^2.0.0: +fast-safe-stringify@^2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz#04b26106cc56681f51a044cfc0d76cf0008ac2c2" + integrity sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg== + +fb-watchman@2.0.0, fb-watchman@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" integrity sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg= @@ -3950,6 +4043,11 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" +fecha@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" + integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg== + fetch-mock@^5.13.1: version "5.13.1" resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-5.13.1.tgz#955794a77f3d972f1644b9ace65a0fdfd60f1df7" @@ -4480,6 +4578,18 @@ glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glo once "^1.3.0" path-is-absolute "^1.0.0" +glob@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^4.3.1: version "4.5.3" resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" @@ -4653,55 +4763,63 @@ graphql-anywhere@^4.1.16: dependencies: apollo-utilities "^1.0.18" -graphql-code-generator@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/graphql-code-generator/-/graphql-code-generator-0.10.1.tgz#b1c05b726ad4c9078a609233cb364eeeb512b5ab" - integrity sha1-scBbcmrUyQeKYJIzyzZO7rUStas= +graphql-code-generator@^0.12.6: + version "0.12.6" + resolved "https://registry.yarnpkg.com/graphql-code-generator/-/graphql-code-generator-0.12.6.tgz#72835046d6c833834d616135c59ad19b7af85d24" + integrity sha512-GSU06AJ4sPsqVKpKzK2KfQM+D2WtnGoazey5iLVUfef5LSYNJqFGsCHMN6bTcCoBLUTXs5gq79yaQvJp5P2gbg== dependencies: + "@graphql-modules/epoxy" "0.1.6" "@types/babylon" "6.16.3" - "@types/prettier" "1.13.1" + "@types/is-glob" "4.0.0" + "@types/prettier" "1.13.2" "@types/valid-url" "1.0.2" babel-types "7.0.0-beta.3" babylon "7.0.0-beta.47" - commander "2.16.0" - glob "7.1.2" - graphql-codegen-compiler "0.10.1" - graphql-codegen-core "0.10.1" + commander "2.18.0" + fb-watchman "2.0.0" + glob "7.1.3" + graphql-codegen-compiler "0.12.6" + graphql-codegen-core "0.12.6" + graphql-import "0.7.1" + is-glob "4.0.0" + is-valid-path "0.1.1" mkdirp "0.5.1" - prettier "1.13.7" - request "2.87.0" + pify "4.0.0" + prettier "1.14.3" + request "2.88.0" valid-url "1.0.9" -graphql-codegen-compiler@0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/graphql-codegen-compiler/-/graphql-codegen-compiler-0.10.1.tgz#de9a6dd037a665a1ad276b3366987e8a5f58bb9f" - integrity sha1-3ppt0DemZaGtJ2szZph+il9Yu58= +graphql-codegen-compiler@0.12.6: + version "0.12.6" + resolved "https://registry.yarnpkg.com/graphql-codegen-compiler/-/graphql-codegen-compiler-0.12.6.tgz#26b8e4c0fe4003c7feb9091d6b259c2505dd837d" + integrity sha512-f/GP19sMsXggdQHZLb7uZVaiuhDX/EqadeaZI9bRfPaALK02hLIOizTYQBKOVMExSmgj3Hiz6DGgu61gRO6z1w== dependencies: - "@types/handlebars" "4.0.38" + "@types/handlebars" "4.0.39" change-case "3.0.2" common-tags "1.8.0" - graphql-codegen-core "0.10.1" - handlebars "4.0.11" + graphql-codegen-core "0.12.6" + handlebars "4.0.12" moment "2.22.2" -graphql-codegen-core@0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/graphql-codegen-core/-/graphql-codegen-core-0.10.1.tgz#f7ab790b050f14b133c5a28f2260cdd8d1e7792d" - integrity sha1-96t5CwUPFLEzxaKPImDN2NHneS0= +graphql-codegen-core@0.12.6: + version "0.12.6" + resolved "https://registry.yarnpkg.com/graphql-codegen-core/-/graphql-codegen-core-0.12.6.tgz#ab3d4d9b0bd10268de749ae215eba84f7bb98fdb" + integrity sha512-yRdLsPEd3b4bJ9aYalGVwa7XKNNK3DP85Yq0nCPcqbfj+OnR7xiVNbfd/oshQ4Mb05QOzSw7sDKBVluA0WTXog== dependencies: graphql-tag "2.9.2" - graphql-tools "3.0.4" - winston "2.4.3" + graphql-tools "4.0.0" + ts-log "2.1.3" + winston "3.1.0" -graphql-codegen-introspection-template@^0.10.5: - version "0.10.5" - resolved "https://registry.yarnpkg.com/graphql-codegen-introspection-template/-/graphql-codegen-introspection-template-0.10.5.tgz#c8c0f647a9771c4e3c6ffbe26aa8da15af9af661" - integrity sha1-yMD2R6l3HE48b/viaqjaFa+a9mE= +graphql-codegen-introspection-template@^0.12.6: + version "0.12.6" + resolved "https://registry.yarnpkg.com/graphql-codegen-introspection-template/-/graphql-codegen-introspection-template-0.12.6.tgz#57e37384d1dd7fba14a0ac4fc8c989f56a358d8c" + integrity sha512-oHgsqz17oxBC84KT/srzBMCN4fByhe0yMMXaEiUuNQyrcj26eWP2qQBygheYAlcED3cYQ/eW8PL9dwphWxT9iQ== -graphql-codegen-typescript-template@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/graphql-codegen-typescript-template/-/graphql-codegen-typescript-template-0.10.1.tgz#bb631d2ebfcd8f15117ca642a9988bd767183e02" - integrity sha1-u2MdLr/NjxURfKZCqZiL12cYPgI= +graphql-codegen-typescript-template@^0.12.6: + version "0.12.6" + resolved "https://registry.yarnpkg.com/graphql-codegen-typescript-template/-/graphql-codegen-typescript-template-0.12.6.tgz#3120089a7aea93373261377b0e0f89792c8bb5ad" + integrity sha512-Qn0EvuFwOmUy4drNEugczNdSmpNtFjJ3oJAdvLW1nnM54GNMlm+9LJLmZfifjG1uB4nvKORV9+v3dY2fKrxaKQ== graphql-extensions@^0.0.x, graphql-extensions@~0.0.9: version "0.0.10" @@ -4716,12 +4834,42 @@ graphql-fields@^1.0.2: resolved "https://registry.yarnpkg.com/graphql-fields/-/graphql-fields-1.0.2.tgz#099ee1d4445b42d0f47e06d622acebb33abc6cce" integrity sha1-CZ7h1ERbQtD0fgbWIqzrszq8bM4= +graphql-import@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/graphql-import/-/graphql-import-0.7.1.tgz#4add8d91a5f752d764b0a4a7a461fcd93136f223" + integrity sha512-YpwpaPjRUVlw2SN3OPljpWbVRWAhMAyfSba5U47qGMOSsPLi2gYeJtngGpymjm9nk57RFWEpjqwh4+dpYuFAPw== + dependencies: + lodash "^4.17.4" + resolve-from "^4.0.0" + graphql-tag@2.9.2, graphql-tag@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.9.2.tgz#2f60a5a981375f430bf1e6e95992427dc18af686" integrity sha512-qnNmof9pAqj/LUzs3lStP0Gw1qhdVCUS7Ab7+SUB6KD5aX1uqxWQRwMnOGTkhKuLvLNIs1TvNz+iS9kUGl1MhA== -graphql-tools@3.0.4, graphql-tools@^3.0.2: +graphql-tools@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-3.1.1.tgz#d593358f01e7c8b1671a17b70ddb034dea9dbc50" + integrity sha512-yHvPkweUB0+Q/GWH5wIG60bpt8CTwBklCSzQdEHmRUgAdEQKxw+9B7zB3dG7wB3Ym7M7lfrS4Ej+jtDZfA2UXg== + dependencies: + apollo-link "^1.2.2" + apollo-utilities "^1.0.1" + deprecated-decorator "^0.1.6" + iterall "^1.1.3" + uuid "^3.1.0" + +graphql-tools@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-4.0.0.tgz#6ea01937c6f947212f83567ba687e97c22fdd2a6" + integrity sha512-WokvjkanuZwY4BZBS3SlkDjrjCPu7WlCtLB2i9JiiXembVEkNos3Rl90zf7sJu72zSidGzTXU63iXRO2Fg3TtA== + dependencies: + apollo-link "^1.2.3" + apollo-utilities "^1.0.1" + deprecated-decorator "^0.1.6" + iterall "^1.1.3" + uuid "^3.1.0" + +graphql-tools@^3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-3.0.4.tgz#d08aa75db111d704cba05d92afd67ec5d1dc6b24" integrity sha512-doQeqej/1D7kowXjYaAKk9H04KZN6+Vm6/KqXk3iqq8kfeI5edHOCg+eDc4LZXb0EEue0vy76JW7TLOwf9ZMZQ== @@ -4845,18 +4993,7 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" -handlebars@4.0.11, handlebars@^4.0.3: - version "4.0.11" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" - integrity sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw= - dependencies: - async "^1.4.0" - optimist "^0.6.1" - source-map "^0.4.4" - optionalDependencies: - uglify-js "^2.6" - -handlebars@^4.0.10: +handlebars@4.0.12, handlebars@^4.0.10: version "4.0.12" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5" integrity sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA== @@ -4867,6 +5004,17 @@ handlebars@^4.0.10: optionalDependencies: uglify-js "^3.1.4" +handlebars@^4.0.3: + version "4.0.11" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" + integrity sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw= + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + hapi-auth-cookie@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/hapi-auth-cookie/-/hapi-auth-cookie-9.0.0.tgz#3b0af443334e2bd92490ddb17bed16e3e9edfd01" @@ -5451,6 +5599,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -5570,6 +5723,13 @@ is-generator-fn@^1.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-1.0.0.tgz#969d49e1bb3329f6bb7f09089be26578b2ddd46a" integrity sha1-lp1J4bszKfa7fwkIm+JleLLd1Go= +is-glob@4.0.0, is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= + dependencies: + is-extglob "^2.1.1" + is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -5584,12 +5744,12 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" - integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= +is-invalid-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-invalid-path/-/is-invalid-path-0.1.0.tgz#307a855b3cf1a938b44ea70d2c61106053714f34" + integrity sha1-MHqFWzzxqTi0TqcNLGEQYFNxTzQ= dependencies: - is-extglob "^2.1.1" + is-glob "^2.0.0" is-lower-case@^1.1.0: version "1.1.3" @@ -5759,6 +5919,13 @@ is-valid-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao= +is-valid-path@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-valid-path/-/is-valid-path-0.1.1.tgz#110f9ff74c37f663e1ec7915eb451f2db93ac9df" + integrity sha1-EQ+f90w39mPh7HkV60UfLbk6yd8= + dependencies: + is-invalid-path "^0.1.0" + is-windows@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" @@ -5806,7 +5973,7 @@ isomorphic-fetch@2.2.1, isomorphic-fetch@^2.1.1: node-fetch "^1.0.1" whatwg-fetch ">=0.10.0" -isstream@0.1.x, isstream@~0.1.2: +isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= @@ -6506,6 +6673,13 @@ kleur@^2.0.1: resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300" integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ== +kuler@1.0.x: + version "1.0.1" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-1.0.1.tgz#ef7c784f36c9fb6e16dd3150d152677b2b0228a6" + integrity sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ== + dependencies: + colornames "^1.1.1" + lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" @@ -6971,6 +7145,17 @@ lodash@~1.0.1: resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" integrity sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE= +logform@^1.9.0, logform@^1.9.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-1.10.0.tgz#c9d5598714c92b546e23f4e78147c40f1e02012e" + integrity sha512-em5ojIhU18fIMOw/333mD+ZLE2fis0EzXl1ZwHx4iQzmpQi6odNiY/t+ITNr33JZhT9/KEaH+UPIipr6a9EjWg== + dependencies: + colors "^1.2.1" + fast-safe-stringify "^2.0.4" + fecha "^2.3.3" + ms "^2.1.1" + triple-beam "^1.2.0" + loglevel@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" @@ -7906,6 +8091,11 @@ once@~1.3.0: dependencies: wrappy "1" +one-time@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e" + integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4= + onetime@^1.0.0: version "1.1.0" resolved "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" @@ -8340,6 +8530,11 @@ pez@4.x.x: hoek "5.x.x" nigel "3.x.x" +pify@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.0.tgz#db04c982b632fd0df9090d14aaf1c8413cadb695" + integrity sha512-zrSP/KDf9DH3K3VePONoCstgPiYJy9z0SCatZuTpOc7YdnWIqwkWdXOuwlr4uDc7em8QZRsFWsT/685x5InjYg== + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -8497,10 +8692,10 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= -prettier@1.13.7: - version "1.13.7" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281" - integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w== +prettier@1.14.3: + version "1.14.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895" + integrity sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg== pretty-format@^23.6.0: version "23.6.0" @@ -9221,7 +9416,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -9516,31 +9711,31 @@ request@2.81.0, "request@>=2.9.0 <2.82.0": tunnel-agent "^0.6.0" uuid "^3.0.0" -request@2.87.0: - version "2.87.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" - integrity sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw== +request@2.88.0, request@^2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== dependencies: aws-sign2 "~0.7.0" - aws4 "^1.6.0" + aws4 "^1.8.0" caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" + combined-stream "~1.0.6" + extend "~3.0.2" forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" + form-data "~2.3.2" + har-validator "~5.1.0" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" + mime-types "~2.1.19" + oauth-sign "~0.9.0" performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - tough-cookie "~2.3.3" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" tunnel-agent "^0.6.0" - uuid "^3.1.0" + uuid "^3.3.2" request@^2.83.0: version "2.83.0" @@ -9598,32 +9793,6 @@ request@^2.85.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - request@~2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" @@ -9690,6 +9859,11 @@ resolve-from@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" integrity sha1-six699nWiBvItuZTM17rywoYh0g= +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve-options@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" @@ -10074,6 +10248,13 @@ simple-git@^1.91.0: dependencies: debug "^3.1.0" +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + sinon@^5.0.7: version "5.0.7" resolved "https://registry.yarnpkg.com/sinon/-/sinon-5.0.7.tgz#3bded6a73613ccc9e512e20246ced69a27c27dab" @@ -10788,6 +10969,11 @@ text-encoding@^0.6.4: resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" integrity sha1-45mpgiV6J22uQou5KEXLcb3CbRk= +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + throat@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" @@ -11029,6 +11215,11 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= +triple-beam@^1.2.0, triple-beam@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" + integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== + "true-case-path@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.2.tgz#7ec91130924766c7f573be3020c34f8fdfd00d62" @@ -11036,6 +11227,11 @@ trim-right@^1.0.1: dependencies: glob "^6.0.4" +ts-log@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.1.3.tgz#9e30aca1baffe7693a2e4142b8f07ecb01cb8340" + integrity sha512-VIk9+hzE80UjhJcSANst8LGRBpfNh32y9d3LVDMtEqcEb1x0hB71IO0aObNcLJ5VpK5tKeF9uI4pwEco03SkwA== + tslib@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" @@ -11611,17 +11807,43 @@ window-size@^0.2.0: resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" integrity sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU= -winston@2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.3.tgz#7a9fdab371b6d3d9b63a592947846d856948c517" - integrity sha512-GYKuysPz2pxYAVJD2NPsDLP5Z79SDEzPm9/j4tCjkF/n89iBNGBMJcR+dMUqxgPNgoSs6fVygPi+Vl2oxIpBuw== - dependencies: - async "~1.0.0" - colors "1.0.x" - cycle "1.0.x" - eyes "0.1.x" - isstream "0.1.x" +winston-transport@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.2.0.tgz#a20be89edf2ea2ca39ba25f3e50344d73e6520e5" + integrity sha512-0R1bvFqxSlK/ZKTH86nymOuKv/cT1PQBMuDdA7k7f0S9fM44dNH6bXnuxwXPrN8lefJgtZq08BKdyZ0DZIy/rg== + dependencies: + readable-stream "^2.3.6" + triple-beam "^1.2.0" + +winston@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.0.0.tgz#1f0b24a96586798bcf0cd149fb07ed47cb01a1b2" + integrity sha512-7QyfOo1PM5zGL6qma6NIeQQMh71FBg/8fhkSAePqtf5YEi6t+UrPDcUuHhuuUasgso49ccvMEsmqr0GBG2qaMQ== + dependencies: + async "^2.6.0" + diagnostics "^1.0.1" + is-stream "^1.1.0" + logform "^1.9.0" + one-time "0.0.4" + readable-stream "^2.3.6" stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.2.0" + +winston@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.1.0.tgz#80724376aef164e024f316100d5b178d78ac5331" + integrity sha512-FsQfEE+8YIEeuZEYhHDk5cILo1HOcWkGwvoidLrDgPog0r4bser1lEIOco2dN9zpDJ1M88hfDgZvxe5z4xNcwg== + dependencies: + async "^2.6.0" + diagnostics "^1.1.1" + is-stream "^1.1.0" + logform "^1.9.1" + one-time "0.0.4" + readable-stream "^2.3.6" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.2.0" wordwrap@0.0.2: version "0.0.2" @@ -11916,6 +12138,13 @@ yeast@0.1.2: resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= +zen-observable-ts@^0.8.10: + version "0.8.10" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.10.tgz#18e2ce1c89fe026e9621fd83cc05168228fce829" + integrity sha512-5vqMtRggU/2GhePC9OU4sYEWOdvmayp2k3gjPf4F0mXwB3CSbbNznfDUvDJx9O2ZTa1EIXdJhPchQveFKwNXPQ== + dependencies: + zen-observable "^0.8.0" + zen-observable-ts@^0.8.6, zen-observable-ts@^0.8.9: version "0.8.9" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.9.tgz#d3c97af08c0afdca37ebcadf7cc3ee96bda9bab1" From 0fc01afada86408bdd2b99c52b09b7441a9b5305 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 26 Oct 2018 14:00:44 -0400 Subject: [PATCH 6/9] prepend elastic license to generated file --- x-pack/plugins/secops/common/graphql/types.ts | 5 +++++ x-pack/plugins/secops/scripts/gql_gen.json | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/secops/common/graphql/types.ts b/x-pack/plugins/secops/common/graphql/types.ts index 36eb79acf0dd8..a77b4facc5fa5 100644 --- a/x-pack/plugins/secops/common/graphql/types.ts +++ b/x-pack/plugins/secops/common/graphql/types.ts @@ -1,4 +1,9 @@ /* tslint:disable */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ import { GraphQLResolveInfo } from 'graphql'; export type Resolver = ( diff --git a/x-pack/plugins/secops/scripts/gql_gen.json b/x-pack/plugins/secops/scripts/gql_gen.json index 87b8233dd1eeb..b42a69702014a 100644 --- a/x-pack/plugins/secops/scripts/gql_gen.json +++ b/x-pack/plugins/secops/scripts/gql_gen.json @@ -1,6 +1,14 @@ { "flattenTypes": true, - "generatorConfig": {}, + "generatorConfig": { + "prepend": [ + "/*", + " * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one", + " * or more contributor license agreements. Licensed under the Elastic License;", + " * you may not use this file except in compliance with the Elastic License.", + " */" + ] + }, "primitives": { "String": "string", "Int": "number", From f4365135f05d228d576ddb49255c055251ed2dc4 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 26 Oct 2018 14:20:45 -0400 Subject: [PATCH 7/9] wip review --- x-pack/plugins/secops/package.json | 3 +-- .../plugins/secops/public/apps/kibana_app.ts | 1 + .../plugins/secops/public/apps/start_app.tsx | 5 +++-- .../plugins/secops/public/apps/testing_app.ts | 1 + .../public/lib/compose/kibana_compose.ts | 4 ++-- .../public/lib/compose/testing_compose.ts | 4 ++-- .../server/graphql/sources/schema.gql.ts | 6 +++--- x-pack/plugins/secops/server/kibana.index.ts | 4 ++-- .../kibana_configuration_adapter.test.ts | 4 ++-- .../kibana_configuration_adapter.ts | 2 +- x-pack/plugins/secops/yarn.lock | 20 ++++--------------- 11 files changed, 22 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/secops/package.json b/x-pack/plugins/secops/package.json index cb1cdba7da3fe..ced3d1d97cf2c 100644 --- a/x-pack/plugins/secops/package.json +++ b/x-pack/plugins/secops/package.json @@ -6,12 +6,11 @@ "build-graphql-types": "node scripts/generate_types_from_graphql.js" }, "devDependencies": { - "@types/boom": "7.2.0", + "@types/boom": "^7.2.0", "@types/color": "^3.0.0", "@types/lodash": "^4.14.110" }, "dependencies": { - "boom": "3.1.1", "lodash": "^4.17.10" } } diff --git a/x-pack/plugins/secops/public/apps/kibana_app.ts b/x-pack/plugins/secops/public/apps/kibana_app.ts index ac705801175f3..292b02c243e0e 100644 --- a/x-pack/plugins/secops/public/apps/kibana_app.ts +++ b/x-pack/plugins/secops/public/apps/kibana_app.ts @@ -8,4 +8,5 @@ import 'uiExports/autocompleteProviders'; import { compose } from '../lib/compose/kibana_compose'; import { startApp } from './start_app'; + startApp(compose()); diff --git a/x-pack/plugins/secops/public/apps/start_app.tsx b/x-pack/plugins/secops/public/apps/start_app.tsx index 0154531c3275c..36f0a83289f9e 100644 --- a/x-pack/plugins/secops/public/apps/start_app.tsx +++ b/x-pack/plugins/secops/public/apps/start_app.tsx @@ -12,10 +12,11 @@ import { ThemeProvider } from 'styled-components'; // TODO use theme provided from parentApp when kibana supports it import { EuiErrorBoundary } from '@elastic/eui'; import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json'; + import { AppFrontendLibs } from '../lib/lib'; import { PageRouter } from '../routes'; -export async function startApp(libs: AppFrontendLibs) { +export const startApp = async (libs: AppFrontendLibs) => { const history = createHashHistory(); libs.framework.render( @@ -27,4 +28,4 @@ export async function startApp(libs: AppFrontendLibs) { ); -} +}; diff --git a/x-pack/plugins/secops/public/apps/testing_app.ts b/x-pack/plugins/secops/public/apps/testing_app.ts index bcd7d0e592644..0fd944d3f45d6 100644 --- a/x-pack/plugins/secops/public/apps/testing_app.ts +++ b/x-pack/plugins/secops/public/apps/testing_app.ts @@ -6,4 +6,5 @@ import { compose } from '../lib/compose/testing_compose'; import { startApp } from './start_app'; + startApp(compose()); diff --git a/x-pack/plugins/secops/public/lib/compose/kibana_compose.ts b/x-pack/plugins/secops/public/lib/compose/kibana_compose.ts index aca5dfa6e119e..5aa9c3c220666 100644 --- a/x-pack/plugins/secops/public/lib/compose/kibana_compose.ts +++ b/x-pack/plugins/secops/public/lib/compose/kibana_compose.ts @@ -56,9 +56,9 @@ export function compose(): AppFrontendLibs { const apolloClient = new ApolloClient(graphQLOptions); - const infraModule = uiModules.get('app/secops'); + const appModule = uiModules.get('app/secops'); - const framework = new AppKibanaFrameworkAdapter(infraModule, uiRoutes, timezoneProvider); + const framework = new AppKibanaFrameworkAdapter(appModule, uiRoutes, timezoneProvider); const libs: AppFrontendLibs = { apolloClient, diff --git a/x-pack/plugins/secops/public/lib/compose/testing_compose.ts b/x-pack/plugins/secops/public/lib/compose/testing_compose.ts index c6f2fb5b07f4c..2dd320c88cbc4 100644 --- a/x-pack/plugins/secops/public/lib/compose/testing_compose.ts +++ b/x-pack/plugins/secops/public/lib/compose/testing_compose.ts @@ -22,12 +22,12 @@ import { AppKibanaObservableApiAdapter } from '../adapters/observable_api/kibana import { AppFrontendLibs } from '../lib'; export function compose(): AppFrontendLibs { - const infraModule = uiModules.get('app/infa'); + const appModule = uiModules.get('app/secops'); const observableApi = new AppKibanaObservableApiAdapter({ basePath: chrome.getBasePath(), xsrfToken: chrome.getXsrfToken(), }); - const framework = new AppKibanaFrameworkAdapter(infraModule, uiRoutes, timezoneProvider); + const framework = new AppKibanaFrameworkAdapter(appModule, uiRoutes, timezoneProvider); const typeDefs = ` Query {} `; diff --git a/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts b/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts index 2060c71a8e499..f9de01dd0ddce 100644 --- a/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts +++ b/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts @@ -8,9 +8,9 @@ import gql from 'graphql-tag'; export const sourcesSchema = gql` extend type Query { - "Get an infrastructure data source by id" + "Get a security data source by id" source("The id of the source" id: ID!): Source! - "Get a list of all infrastructure data sources" + "Get a list of all security data sources" allSources: [Source!]! } @@ -21,7 +21,7 @@ export const sourcesSchema = gql` configuration: SourceConfiguration! } - "A set of configuration options for an infrastructure data source" + "A set of configuration options for a security data source" type SourceConfiguration { "The field mapping to use for this source" fields: SourceFields! diff --git a/x-pack/plugins/secops/server/kibana.index.ts b/x-pack/plugins/secops/server/kibana.index.ts index 5ab9e7886fac4..645640cad3378 100644 --- a/x-pack/plugins/secops/server/kibana.index.ts +++ b/x-pack/plugins/secops/server/kibana.index.ts @@ -21,7 +21,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { export const getConfigSchema = (Joi: typeof JoiNamespace) => { const DefaultSourceConfigSchema = Joi.object({}); - const InfraRootConfigSchema = Joi.object({ + const AppRootConfigSchema = Joi.object({ enabled: Joi.boolean().default(true), query: Joi.object({ partitionSize: Joi.number(), @@ -35,5 +35,5 @@ export const getConfigSchema = (Joi: typeof JoiNamespace) => { .default(), }).default(); - return InfraRootConfigSchema; + return AppRootConfigSchema; }; diff --git a/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.test.ts b/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.test.ts index 8624df9d52891..07a3ce4bd9e7b 100644 --- a/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.test.ts +++ b/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.test.ts @@ -7,7 +7,7 @@ import { KibanaConfigurationAdapter } from './kibana_configuration_adapter'; describe('the KibanaConfigurationAdapter', () => { - test('queries the xpack.infra configuration of the server', async () => { + test('queries the xpack.secops configuration of the server', async () => { const mockConfig = { get: jest.fn(), }; @@ -18,7 +18,7 @@ describe('the KibanaConfigurationAdapter', () => { await configurationAdapter.get(); - expect(mockConfig.get).toBeCalledWith('xpack.infra'); + expect(mockConfig.get).toBeCalledWith('xpack.secops'); }); test('applies the query defaults', async () => { diff --git a/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.ts b/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.ts index 291c0677cf035..25cabfb4d0af3 100644 --- a/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.ts +++ b/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.ts @@ -27,7 +27,7 @@ export class KibanaConfigurationAdapter throw new Error('Failed to access configuration of server.'); } - const configuration = config.get('xpack.infra') || {}; + const configuration = config.get('xpack.secops') || {}; const configurationWithDefaults = { enabled: true, query: { diff --git a/x-pack/plugins/secops/yarn.lock b/x-pack/plugins/secops/yarn.lock index 3680885de5648..d0e11d6d6d3cd 100644 --- a/x-pack/plugins/secops/yarn.lock +++ b/x-pack/plugins/secops/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/boom@7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/boom/-/boom-7.2.0.tgz#19c36cbb5811a7493f0f2e37f31d42b28df1abc1" - integrity sha512-HonbGsHFbskh9zRAzA6tabcw18mCOsSEOL2ibGAuVqk6e7nElcRmWO5L4UfIHpDbWBWw+eZYFdsQ1+MEGgpcVA== +"@types/boom@^7.2.0": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@types/boom/-/boom-7.2.1.tgz#a21e21ba08cc49d17b26baef98e1a77ee4d6cdb0" + integrity sha512-kOiap+kSa4DPoookJXQGQyKy1rjZ55tgfKAh9F0m1NUdukkcwVzpSnXPMH42a5L+U++ugdQlh/xFJu/WAdr1aw== "@types/color-convert@*": version "1.9.0" @@ -31,18 +31,6 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.117.tgz#695a7f514182771a1e0f4345d189052ee33c8778" integrity sha512-xyf2m6tRbz8qQKcxYZa7PA4SllYcay+eh25DN3jmNYY6gSTL7Htc/bttVdkqj2wfJGbeWlQiX8pIyJpKU+tubw== -boom@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-3.1.1.tgz#b6424f01ed8d492b2b12ae86047c24e8b6a7c937" - integrity sha1-tkJPAe2NSSsrEq6GBHwk6LanyTc= - dependencies: - hoek "3.x.x" - -hoek@3.x.x: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-3.0.4.tgz#268adff66bb6695c69b4789a88b1e0847c3f3123" - integrity sha1-Jorf9mu2aVxptHiaiLHghHw/MSM= - lodash@^4.17.10: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" From 412d03b2b58fec239999ab92446238fb8e2ce100 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 26 Oct 2018 15:32:05 -0400 Subject: [PATCH 8/9] wip review --- .../public/lib/adapters/framework/kibana_framework_adapter.ts | 4 +--- x-pack/plugins/secops/public/lib/lib.ts | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/x-pack/plugins/secops/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/secops/public/lib/adapters/framework/kibana_framework_adapter.ts index e3c1bc9ff0072..d2e745245680b 100644 --- a/x-pack/plugins/secops/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/secops/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -23,7 +23,6 @@ const ROOT_ELEMENT_ID = 'react-secops-root'; const BREADCRUMBS_ELEMENT_ID = 'react-secops-breadcrumbs'; export class AppKibanaFrameworkAdapter implements AppFrameworkAdapter { - public appState: object; public dateFormat?: string; public kbnVersion?: string; public scaledDateFormat?: string; @@ -37,7 +36,6 @@ export class AppKibanaFrameworkAdapter implements AppFrameworkAdapter { constructor(uiModule: IModule, uiRoutes: KibanaUIRoutes, timezoneProvider: AppTimezoneProvider) { this.adapterService = new KibanaAdapterServiceProvider(); this.timezoneProvider = timezoneProvider; - this.appState = {}; this.register(uiModule, uiRoutes); } @@ -122,7 +120,7 @@ export class AppKibanaFrameworkAdapter implements AppFrameworkAdapter { config: AppKibanaUIConfig, kbnVersion: string, Private: (provider: Provider) => Provider, - // @ts-ignore: inject kibanaAdapter to force eager instatiation + // @ts-ignore: inject kibanaAdapter to force eager instaliation kibanaAdapter: any ) => { this.timezone = Private(this.timezoneProvider)(); diff --git a/x-pack/plugins/secops/public/lib/lib.ts b/x-pack/plugins/secops/public/lib/lib.ts index e9ec1258c538a..da1bc5b2fb10c 100644 --- a/x-pack/plugins/secops/public/lib/lib.ts +++ b/x-pack/plugins/secops/public/lib/lib.ts @@ -21,14 +21,12 @@ export type AppTimezoneProvider = () => string; export type AppApolloClient = ApolloClient; export interface AppFrameworkAdapter { - // Insstance vars appState?: object; dateFormat?: string; kbnVersion?: string; scaledDateFormat?: string; timezone?: string; - // Methods setUISettings(key: string, value: any): void; render(component: React.ReactElement): void; renderBreadcrumbs(component: React.ReactElement): void; From 86a100c3a1e4d01aa4c357705b77142db3f1f417 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 26 Oct 2018 16:09:53 -0400 Subject: [PATCH 9/9] wip review fix miss spelling + add a new line between elastic license and code --- .../secops/common/graphql/introspection.json | 6 +++--- x-pack/plugins/secops/common/graphql/types.ts | 13 +++++++------ x-pack/plugins/secops/public/lib/lib.ts | 2 +- x-pack/plugins/secops/scripts/gql_gen.json | 3 ++- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/secops/common/graphql/introspection.json b/x-pack/plugins/secops/common/graphql/introspection.json index 702f9eee484cd..7a54bdae896c1 100644 --- a/x-pack/plugins/secops/common/graphql/introspection.json +++ b/x-pack/plugins/secops/common/graphql/introspection.json @@ -11,7 +11,7 @@ "fields": [ { "name": "source", - "description": "Get an infrastructure data source by id", + "description": "Get a security data source by id", "args": [ { "name": "id", @@ -34,7 +34,7 @@ }, { "name": "allSources", - "description": "Get a list of all infrastructure data sources", + "description": "Get a list of all security data sources", "args": [], "type": { "kind": "NON_NULL", @@ -106,7 +106,7 @@ { "kind": "OBJECT", "name": "SourceConfiguration", - "description": "A set of configuration options for an infrastructure data source", + "description": "A set of configuration options for a security data source", "fields": [ { "name": "fields", diff --git a/x-pack/plugins/secops/common/graphql/types.ts b/x-pack/plugins/secops/common/graphql/types.ts index a77b4facc5fa5..ed44e8d5e4b53 100644 --- a/x-pack/plugins/secops/common/graphql/types.ts +++ b/x-pack/plugins/secops/common/graphql/types.ts @@ -4,6 +4,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { GraphQLResolveInfo } from 'graphql'; export type Resolver = ( @@ -29,15 +30,15 @@ export type SubscriptionResolver { - source?: SourceResolver /** Get an infrastructure data source by id */; + source?: SourceResolver /** Get a security data source by id */; allSources?: AllSourcesResolver< Source[], any, Context - > /** Get a list of all infrastructure data sources */; + > /** Get a list of all security data sources */; } export type SourceResolver = Resolver< @@ -98,7 +99,7 @@ export namespace SourceResolvers { Context = any > = Resolver; } -/** A set of configuration options for an infrastructure data source */ +/** A set of configuration options for a security data source */ export namespace SourceConfigurationResolvers { export interface Resolvers { fields?: FieldsResolver< diff --git a/x-pack/plugins/secops/public/lib/lib.ts b/x-pack/plugins/secops/public/lib/lib.ts index da1bc5b2fb10c..5246d4c1e1f80 100644 --- a/x-pack/plugins/secops/public/lib/lib.ts +++ b/x-pack/plugins/secops/public/lib/lib.ts @@ -32,7 +32,7 @@ export interface AppFrameworkAdapter { renderBreadcrumbs(component: React.ReactElement): void; } -export interface AppFramworkAdapterConstructable { +export interface AppFrameworkAdapterConstructible { new (uiModule: IModule, timezoneProvider: AppTimezoneProvider): AppFrameworkAdapter; } diff --git a/x-pack/plugins/secops/scripts/gql_gen.json b/x-pack/plugins/secops/scripts/gql_gen.json index b42a69702014a..ecfeb9f9e124b 100644 --- a/x-pack/plugins/secops/scripts/gql_gen.json +++ b/x-pack/plugins/secops/scripts/gql_gen.json @@ -6,7 +6,8 @@ " * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one", " * or more contributor license agreements. Licensed under the Elastic License;", " * you may not use this file except in compliance with the Elastic License.", - " */" + " */", + "" ] }, "primitives": {