Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions x-pack/plugins/secops/common/graphql/introspection.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "whoAmI",
"description": "Just a simple example to get the app name",
"args": [],
"type": { "kind": "OBJECT", "name": "SayMyName", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
Expand Down Expand Up @@ -227,6 +235,29 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SayMyName",
"description": "",
"fields": [
{
"name": "appName",
"description": "The id of the source",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "__Schema",
Expand Down
48 changes: 48 additions & 0 deletions x-pack/plugins/secops/common/graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface Query {
export interface Source {
id: string /** The id of the source */;
configuration: SourceConfiguration /** The raw configuration of the source */;
whoAmI?: SayMyName | null /** Just a simple example to get the app name */;
}
/** A set of configuration options for a security data source */
export interface SourceConfiguration {
Expand All @@ -51,6 +52,10 @@ export interface SourceFields {
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 SayMyName {
appName: string /** The id of the source */;
}
export interface SourceQueryArgs {
id: string /** The id of the source */;
}
Expand Down Expand Up @@ -90,6 +95,11 @@ export namespace SourceResolvers {
any,
Context
> /** The raw configuration of the source */;
whoAmI?: WhoAmIResolver<
SayMyName | null,
any,
Context
> /** Just a simple example to get the app name */;
}

export type IdResolver<R = string, Parent = any, Context = any> = Resolver<R, Parent, Context>;
Expand All @@ -98,6 +108,11 @@ export namespace SourceResolvers {
Parent = any,
Context = any
> = Resolver<R, Parent, Context>;
export type WhoAmIResolver<R = SayMyName | null, Parent = any, Context = any> = Resolver<
R,
Parent,
Context
>;
}
/** A set of configuration options for a security data source */
export namespace SourceConfigurationResolvers {
Expand Down Expand Up @@ -161,3 +176,36 @@ export namespace SourceFieldsResolvers {
Context
>;
}

export namespace SayMyNameResolvers {
export interface Resolvers<Context = any> {
appName?: AppNameResolver<string, any, Context> /** The id of the source */;
}

export type AppNameResolver<R = string, Parent = any, Context = any> = Resolver<
R,
Parent,
Context
>;
}

export namespace WhoAmIQuery {
export type Variables = {
sourceId: string;
};

export type Query = {
__typename?: 'Query';
source: Source;
};

export type Source = {
__typename?: 'Source';
whoAmI?: WhoAmI | null;
};

export type WhoAmI = {
__typename?: 'SayMyName';
appName: string;
};
}
37 changes: 37 additions & 0 deletions x-pack/plugins/secops/public/containers/who_am_i/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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 { getOr } from 'lodash/fp';
import React from 'react';
import { Query } from 'react-apollo';

import { WhoAmIQuery } from '../../../common/graphql/types';

import { whoAmIQuery } from './who_am_i.gql_query';

interface WhoAmIArgs {
appName: string;
}

interface WHoAmIProps {
children: (args: WhoAmIArgs) => React.ReactNode;
sourceId: string;
}

export const WhoAmI = ({ children, sourceId }: WHoAmIProps) => (
<Query<WhoAmIQuery.Query, WhoAmIQuery.Variables>
query={whoAmIQuery}
fetchPolicy="no-cache"
notifyOnNetworkStatusChange
variables={{ sourceId }}
>
{({ data }) =>
children({
appName: getOr('Who am I ?', 'source.whoAmI.appName', data),
})
}
</Query>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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 whoAmIQuery = gql`
query WhoAmIQuery($sourceId: ID!) {
source(id: $sourceId) {
whoAmI {
appName
}
}
}
`;
3 changes: 2 additions & 1 deletion x-pack/plugins/secops/public/pages/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import * as React from 'react';
import { pure } from 'recompose';

import { ColumnarPage } from '../../components/page';
import { WhoAmI } from '../../containers/who_am_i';

export const HomePage = pure(() => (
<ColumnarPage>
<h1>Hello Sec Ops</h1>
<WhoAmI sourceId="default">{({ appName }) => <h1>Hello {appName}</h1>}</WhoAmI>
</ColumnarPage>
));
39 changes: 7 additions & 32 deletions x-pack/plugins/secops/server/graphql/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
*/

import { rootSchema } from '../../common/graphql/root/schema.gql';
import sourceMock from '../graphql/sources/source.mock';
import sourcesMock from '../graphql/sources/sources.mock';
import { getSourceQueryMock } from '../graphql/sources/source.mock';
import { getAllSourcesQueryMock } from '../graphql/sources/sources.mock';
import { Logger } from '../utils/logger';
import { sourcesSchema } from './sources/schema.gql';
import { whoAmISchema } from './who_am_i/schema.gql';

export const schemas = [rootSchema, sourcesSchema];
export const schemas = [rootSchema, sourcesSchema, whoAmISchema];

// The types from graphql-tools/src/mock.ts 'any' based. I add slightly
// stricter types here, but these should go away when graphql-tools using something
// other than "any" in the future for its types.
// https://github.com/apollographql/graphql-tools/blob/master/src/mock.ts#L406
interface Context {
export interface Context {
req: {
payload: {
operationName: string;
Expand All @@ -26,33 +27,7 @@ interface Context {

export const createMocks = (logger: Logger) => ({
Query: () => ({
allSources: (root: unknown, args: unknown, context: Context) => {
logger.info('Mock allSources');
const operationName = context.req.payload.operationName.toLowerCase();
switch (operationName) {
case 'test': {
logger.info(`Using mock for test ${sourceMock}`);
return sourcesMock;
}
default: {
logger.error(`Could not find a mock for: ${operationName}`);
return [];
}
}
},
source: (root: unknown, args: unknown, context: Context) => {
logger.info('Mock source');
const operationName = context.req.payload.operationName.toLowerCase();
switch (operationName) {
case 'test': {
logger.info(`Using mock for test ${sourceMock}`);
return sourceMock;
}
default: {
logger.error(`Could not find a mock for: ${operationName}`);
return {};
}
}
},
...getAllSourcesQueryMock(logger),
...getSourceQueryMock(logger),
}),
});
44 changes: 44 additions & 0 deletions x-pack/plugins/secops/server/graphql/sources/resolvers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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';
import { Sources, SourcesAdapter } from '../../lib/sources';
import { createSourcesResolvers, SourcesResolversDeps } from './resolvers';
import { mockSourceData } from './source.mock';

const mockGetAll = jest.fn();
mockGetAll.mockResolvedValue({
default: {
...mockSourceData.configuration,
},
});
const mockSourcesAdapter: SourcesAdapter = {
getAll: mockGetAll,
};
const mockLibs: SourcesResolversDeps = {
sources: new Sources(mockSourcesAdapter),
};
const context: any = {
req: {
params: {},
query: {},
payload: {
operationName: 'test',
},
},
};

describe('Test Source Resolvers', () => {
test(`Make sure that getCongiguration have been called`, async () => {
const data = await createSourcesResolvers(mockLibs).Query.source(
null,
{ id: 'default' },
context,
{} as GraphQLResolveInfo
);
expect(mockSourcesAdapter.getAll).toHaveBeenCalled();
expect(data).toEqual(mockSourceData);
});
});
2 changes: 1 addition & 1 deletion x-pack/plugins/secops/server/graphql/sources/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type QueryAllSourcesResolver = AppResolverWithFields<
'id' | 'configuration'
>;

interface SourcesResolversDeps {
export interface SourcesResolversDeps {
sources: Sources;
}

Expand Down
78 changes: 78 additions & 0 deletions x-pack/plugins/secops/server/graphql/sources/schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 { graphql } from 'graphql';
import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools';

import { rootSchema } from '../../../common/graphql/root/schema.gql';
import { Logger } from '../../utils/logger';
import { sourcesSchema } from './schema.gql';
import { getSourceQueryMock, mockSourceData } from './source.mock';

const testCaseSource = {
id: 'Test case to query basic information from source',
query: `
query SourceQuery($sourceId: ID!) {
source(id: $sourceId) {
id
configuration {
fields {
host
}
}
}
}
`,
variables: { sourceId: 'default' },
context: {
req: {
payload: {
operationName: 'test',
},
},
},
expected: {
data: {
source: {
...mockSourceData,
},
},
},
};

describe('Test Source Schema', () => {
// Array of case types
const cases = [testCaseSource];
const typeDefs = [rootSchema, sourcesSchema];
const mockSchema = makeExecutableSchema({ typeDefs });

// Here we specify the return payloads of mocked types
const logger: Logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};
const mocks = {
Query: () => ({
...getSourceQueryMock(logger),
}),
};

addMockFunctionsToSchema({
schema: mockSchema,
mocks,
});

cases.forEach(obj => {
const { id, query, variables, context, expected } = obj;

test(`${id}`, async () => {
const result = await graphql(mockSchema, query, null, context, variables);
return await expect(result).toEqual(expected);
});
});
});
Loading