Skip to content

Commit a69f4d4

Browse files
XavierMandrew-goldstein
authored andcommitted
Simple query (#24745)
* 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
1 parent a022bcd commit a69f4d4

File tree

15 files changed

+374
-48
lines changed

15 files changed

+374
-48
lines changed

x-pack/plugins/secops/common/graphql/introspection.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@
9696
},
9797
"isDeprecated": false,
9898
"deprecationReason": null
99+
},
100+
{
101+
"name": "whoAmI",
102+
"description": "Just a simple example to get the app name",
103+
"args": [],
104+
"type": { "kind": "OBJECT", "name": "SayMyName", "ofType": null },
105+
"isDeprecated": false,
106+
"deprecationReason": null
99107
}
100108
],
101109
"inputFields": null,
@@ -227,6 +235,29 @@
227235
"enumValues": null,
228236
"possibleTypes": null
229237
},
238+
{
239+
"kind": "OBJECT",
240+
"name": "SayMyName",
241+
"description": "",
242+
"fields": [
243+
{
244+
"name": "appName",
245+
"description": "The id of the source",
246+
"args": [],
247+
"type": {
248+
"kind": "NON_NULL",
249+
"name": null,
250+
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
251+
},
252+
"isDeprecated": false,
253+
"deprecationReason": null
254+
}
255+
],
256+
"inputFields": null,
257+
"interfaces": [],
258+
"enumValues": null,
259+
"possibleTypes": null
260+
},
230261
{
231262
"kind": "OBJECT",
232263
"name": "__Schema",

x-pack/plugins/secops/common/graphql/types.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export interface Query {
3737
export interface Source {
3838
id: string /** The id of the source */;
3939
configuration: SourceConfiguration /** The raw configuration of the source */;
40+
whoAmI?: SayMyName | null /** Just a simple example to get the app name */;
4041
}
4142
/** A set of configuration options for a security data source */
4243
export interface SourceConfiguration {
@@ -51,6 +52,10 @@ export interface SourceFields {
5152
tiebreaker: string /** The field to use as a tiebreaker for log events that have identical timestamps */;
5253
timestamp: string /** The field to use as a timestamp for metrics and logs */;
5354
}
55+
56+
export interface SayMyName {
57+
appName: string /** The id of the source */;
58+
}
5459
export interface SourceQueryArgs {
5560
id: string /** The id of the source */;
5661
}
@@ -90,6 +95,11 @@ export namespace SourceResolvers {
9095
any,
9196
Context
9297
> /** The raw configuration of the source */;
98+
whoAmI?: WhoAmIResolver<
99+
SayMyName | null,
100+
any,
101+
Context
102+
> /** Just a simple example to get the app name */;
93103
}
94104

95105
export type IdResolver<R = string, Parent = any, Context = any> = Resolver<R, Parent, Context>;
@@ -98,6 +108,11 @@ export namespace SourceResolvers {
98108
Parent = any,
99109
Context = any
100110
> = Resolver<R, Parent, Context>;
111+
export type WhoAmIResolver<R = SayMyName | null, Parent = any, Context = any> = Resolver<
112+
R,
113+
Parent,
114+
Context
115+
>;
101116
}
102117
/** A set of configuration options for a security data source */
103118
export namespace SourceConfigurationResolvers {
@@ -161,3 +176,36 @@ export namespace SourceFieldsResolvers {
161176
Context
162177
>;
163178
}
179+
180+
export namespace SayMyNameResolvers {
181+
export interface Resolvers<Context = any> {
182+
appName?: AppNameResolver<string, any, Context> /** The id of the source */;
183+
}
184+
185+
export type AppNameResolver<R = string, Parent = any, Context = any> = Resolver<
186+
R,
187+
Parent,
188+
Context
189+
>;
190+
}
191+
192+
export namespace WhoAmIQuery {
193+
export type Variables = {
194+
sourceId: string;
195+
};
196+
197+
export type Query = {
198+
__typename?: 'Query';
199+
source: Source;
200+
};
201+
202+
export type Source = {
203+
__typename?: 'Source';
204+
whoAmI?: WhoAmI | null;
205+
};
206+
207+
export type WhoAmI = {
208+
__typename?: 'SayMyName';
209+
appName: string;
210+
};
211+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { getOr } from 'lodash/fp';
8+
import React from 'react';
9+
import { Query } from 'react-apollo';
10+
11+
import { WhoAmIQuery } from '../../../common/graphql/types';
12+
13+
import { whoAmIQuery } from './who_am_i.gql_query';
14+
15+
interface WhoAmIArgs {
16+
appName: string;
17+
}
18+
19+
interface WHoAmIProps {
20+
children: (args: WhoAmIArgs) => React.ReactNode;
21+
sourceId: string;
22+
}
23+
24+
export const WhoAmI = ({ children, sourceId }: WHoAmIProps) => (
25+
<Query<WhoAmIQuery.Query, WhoAmIQuery.Variables>
26+
query={whoAmIQuery}
27+
fetchPolicy="no-cache"
28+
notifyOnNetworkStatusChange
29+
variables={{ sourceId }}
30+
>
31+
{({ data }) =>
32+
children({
33+
appName: getOr('Who am I ?', 'source.whoAmI.appName', data),
34+
})
35+
}
36+
</Query>
37+
);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import gql from 'graphql-tag';
8+
9+
export const whoAmIQuery = gql`
10+
query WhoAmIQuery($sourceId: ID!) {
11+
source(id: $sourceId) {
12+
whoAmI {
13+
appName
14+
}
15+
}
16+
}
17+
`;

x-pack/plugins/secops/public/pages/home/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import * as React from 'react';
88
import { pure } from 'recompose';
99

1010
import { ColumnarPage } from '../../components/page';
11+
import { WhoAmI } from '../../containers/who_am_i';
1112

1213
export const HomePage = pure(() => (
1314
<ColumnarPage>
14-
<h1>Hello Sec Ops</h1>
15+
<WhoAmI sourceId="default">{({ appName }) => <h1>Hello {appName}</h1>}</WhoAmI>
1516
</ColumnarPage>
1617
));

x-pack/plugins/secops/server/graphql/index.ts

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@
55
*/
66

77
import { rootSchema } from '../../common/graphql/root/schema.gql';
8-
import sourceMock from '../graphql/sources/source.mock';
9-
import sourcesMock from '../graphql/sources/sources.mock';
8+
import { getSourceQueryMock } from '../graphql/sources/source.mock';
9+
import { getAllSourcesQueryMock } from '../graphql/sources/sources.mock';
1010
import { Logger } from '../utils/logger';
1111
import { sourcesSchema } from './sources/schema.gql';
12+
import { whoAmISchema } from './who_am_i/schema.gql';
1213

13-
export const schemas = [rootSchema, sourcesSchema];
14+
export const schemas = [rootSchema, sourcesSchema, whoAmISchema];
1415

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

2728
export const createMocks = (logger: Logger) => ({
2829
Query: () => ({
29-
allSources: (root: unknown, args: unknown, context: Context) => {
30-
logger.info('Mock allSources');
31-
const operationName = context.req.payload.operationName.toLowerCase();
32-
switch (operationName) {
33-
case 'test': {
34-
logger.info(`Using mock for test ${sourceMock}`);
35-
return sourcesMock;
36-
}
37-
default: {
38-
logger.error(`Could not find a mock for: ${operationName}`);
39-
return [];
40-
}
41-
}
42-
},
43-
source: (root: unknown, args: unknown, context: Context) => {
44-
logger.info('Mock source');
45-
const operationName = context.req.payload.operationName.toLowerCase();
46-
switch (operationName) {
47-
case 'test': {
48-
logger.info(`Using mock for test ${sourceMock}`);
49-
return sourceMock;
50-
}
51-
default: {
52-
logger.error(`Could not find a mock for: ${operationName}`);
53-
return {};
54-
}
55-
}
56-
},
30+
...getAllSourcesQueryMock(logger),
31+
...getSourceQueryMock(logger),
5732
}),
5833
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import { GraphQLResolveInfo } from 'graphql';
7+
import { Sources, SourcesAdapter } from '../../lib/sources';
8+
import { createSourcesResolvers, SourcesResolversDeps } from './resolvers';
9+
import { mockSourceData } from './source.mock';
10+
11+
const mockGetAll = jest.fn();
12+
mockGetAll.mockResolvedValue({
13+
default: {
14+
...mockSourceData.configuration,
15+
},
16+
});
17+
const mockSourcesAdapter: SourcesAdapter = {
18+
getAll: mockGetAll,
19+
};
20+
const mockLibs: SourcesResolversDeps = {
21+
sources: new Sources(mockSourcesAdapter),
22+
};
23+
const context: any = {
24+
req: {
25+
params: {},
26+
query: {},
27+
payload: {
28+
operationName: 'test',
29+
},
30+
},
31+
};
32+
33+
describe('Test Source Resolvers', () => {
34+
test(`Make sure that getCongiguration have been called`, async () => {
35+
const data = await createSourcesResolvers(mockLibs).Query.source(
36+
null,
37+
{ id: 'default' },
38+
context,
39+
{} as GraphQLResolveInfo
40+
);
41+
expect(mockSourcesAdapter.getAll).toHaveBeenCalled();
42+
expect(data).toEqual(mockSourceData);
43+
});
44+
});

x-pack/plugins/secops/server/graphql/sources/resolvers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export type QueryAllSourcesResolver = AppResolverWithFields<
2323
'id' | 'configuration'
2424
>;
2525

26-
interface SourcesResolversDeps {
26+
export interface SourcesResolversDeps {
2727
sources: Sources;
2828
}
2929

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { graphql } from 'graphql';
8+
import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools';
9+
10+
import { rootSchema } from '../../../common/graphql/root/schema.gql';
11+
import { Logger } from '../../utils/logger';
12+
import { sourcesSchema } from './schema.gql';
13+
import { getSourceQueryMock, mockSourceData } from './source.mock';
14+
15+
const testCaseSource = {
16+
id: 'Test case to query basic information from source',
17+
query: `
18+
query SourceQuery($sourceId: ID!) {
19+
source(id: $sourceId) {
20+
id
21+
configuration {
22+
fields {
23+
host
24+
}
25+
}
26+
}
27+
}
28+
`,
29+
variables: { sourceId: 'default' },
30+
context: {
31+
req: {
32+
payload: {
33+
operationName: 'test',
34+
},
35+
},
36+
},
37+
expected: {
38+
data: {
39+
source: {
40+
...mockSourceData,
41+
},
42+
},
43+
},
44+
};
45+
46+
describe('Test Source Schema', () => {
47+
// Array of case types
48+
const cases = [testCaseSource];
49+
const typeDefs = [rootSchema, sourcesSchema];
50+
const mockSchema = makeExecutableSchema({ typeDefs });
51+
52+
// Here we specify the return payloads of mocked types
53+
const logger: Logger = {
54+
debug: jest.fn(),
55+
info: jest.fn(),
56+
warn: jest.fn(),
57+
error: jest.fn(),
58+
};
59+
const mocks = {
60+
Query: () => ({
61+
...getSourceQueryMock(logger),
62+
}),
63+
};
64+
65+
addMockFunctionsToSchema({
66+
schema: mockSchema,
67+
mocks,
68+
});
69+
70+
cases.forEach(obj => {
71+
const { id, query, variables, context, expected } = obj;
72+
73+
test(`${id}`, async () => {
74+
const result = await graphql(mockSchema, query, null, context, variables);
75+
return await expect(result).toEqual(expected);
76+
});
77+
});
78+
});

0 commit comments

Comments
 (0)