Skip to content

Commit

Permalink
Simple query (#24745)
Browse files Browse the repository at this point in the history
* Translations for Region Map (#23875)

add translations for region_map plugin

* Translations for Table Vis plugin (#23679)

add translations for table vis plugin

* add a simple example/flow to see what need to be done to query with graphql

* work review

* simple test for source schema

* fix some reviewing and add resolver test code for source

* review by Andrew G
  • Loading branch information
XavierM authored and andrew-goldstein committed Dec 4, 2018
1 parent a022bcd commit a69f4d4
Show file tree
Hide file tree
Showing 15 changed files with 374 additions and 48 deletions.
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

0 comments on commit a69f4d4

Please sign in to comment.