Skip to content

Commit 86d7050

Browse files
[Telemetry] Add Application Usage Schema (#75283)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent 789b67f commit 86d7050

14 files changed

+1650
-88
lines changed

.telemetryrc.json

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"src/plugins/testbed/",
88
"src/plugins/kibana_utils/",
99
"src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts",
10-
"src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts",
1110
"src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts",
1211
"src/plugins/kibana_usage_collection/server/collectors/ui_metric/telemetry_ui_metric_collector.ts",
1312
"src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts"

packages/kbn-telemetry-tools/src/tools/__fixture__/mock_schema.json

+16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@
55
"flat": {
66
"type": "keyword"
77
},
8+
"my_index_signature_prop": {
9+
"properties": {
10+
"avg": {
11+
"type": "number"
12+
},
13+
"count": {
14+
"type": "number"
15+
},
16+
"max": {
17+
"type": "number"
18+
},
19+
"min": {
20+
"type": "number"
21+
}
22+
}
23+
},
824
"my_str": {
925
"type": "text"
1026
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { SyntaxKind } from 'typescript';
21+
import { ParsedUsageCollection } from '../ts_parser';
22+
23+
export const parsedIndexedInterfaceWithNoMatchingSchema: ParsedUsageCollection = [
24+
'src/fixtures/telemetry_collectors/indexed_interface_with_not_matching_schema.ts',
25+
{
26+
collectorName: 'indexed_interface_with_not_matching_schema',
27+
schema: {
28+
value: {
29+
something: {
30+
count_1: {
31+
type: 'number',
32+
},
33+
},
34+
},
35+
},
36+
fetch: {
37+
typeName: 'Usage',
38+
typeDescriptor: {
39+
'': {
40+
'@@INDEX@@': {
41+
count_1: {
42+
kind: SyntaxKind.NumberKeyword,
43+
type: 'NumberKeyword',
44+
},
45+
count_2: {
46+
kind: SyntaxKind.NumberKeyword,
47+
type: 'NumberKeyword',
48+
},
49+
},
50+
},
51+
},
52+
},
53+
},
54+
];

packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts

+22
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ export const parsedWorkingCollector: ParsedUsageCollection = [
3232
my_str: {
3333
type: 'text',
3434
},
35+
my_index_signature_prop: {
36+
avg: {
37+
type: 'number',
38+
},
39+
count: {
40+
type: 'number',
41+
},
42+
max: {
43+
type: 'number',
44+
},
45+
min: {
46+
type: 'number',
47+
},
48+
},
3549
my_objects: {
3650
total: {
3751
type: 'number',
@@ -60,6 +74,14 @@ export const parsedWorkingCollector: ParsedUsageCollection = [
6074
kind: SyntaxKind.StringKeyword,
6175
type: 'StringKeyword',
6276
},
77+
my_index_signature_prop: {
78+
'': {
79+
'@@INDEX@@': {
80+
kind: SyntaxKind.NumberKeyword,
81+
type: 'NumberKeyword',
82+
},
83+
},
84+
},
6385
my_objects: {
6486
total: {
6587
kind: SyntaxKind.NumberKeyword,

packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap

+54
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/kbn-telemetry-tools/src/tools/check_collector__integrity.test.ts

+15
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import { cloneDeep } from 'lodash';
2121
import * as ts from 'typescript';
2222
import { parsedWorkingCollector } from './__fixture__/parsed_working_collector';
23+
import { parsedIndexedInterfaceWithNoMatchingSchema } from './__fixture__/parsed_indexed_interface_with_not_matching_schema';
2324
import { checkCompatibleTypeDescriptor, checkMatchingMapping } from './check_collector_integrity';
2425
import * as path from 'path';
2526
import { readFile } from 'fs';
@@ -82,6 +83,20 @@ describe('checkCompatibleTypeDescriptor', () => {
8283
expect(incompatibles).toHaveLength(0);
8384
});
8485

86+
it('returns diff on indexed interface with no matching schema', () => {
87+
const incompatibles = checkCompatibleTypeDescriptor([
88+
parsedIndexedInterfaceWithNoMatchingSchema,
89+
]);
90+
expect(incompatibles).toHaveLength(1);
91+
const { diff, message } = incompatibles[0];
92+
// eslint-disable-next-line @typescript-eslint/naming-convention
93+
expect(diff).toEqual({ '.@@INDEX@@.count_2.kind': 'number' });
94+
expect(message).toHaveLength(1);
95+
expect(message).toEqual([
96+
'incompatible Type key (Usage..@@INDEX@@.count_2): expected (undefined) got ("number").',
97+
]);
98+
});
99+
85100
describe('Interface Change', () => {
86101
it('returns diff on incompatible type descriptor with mapping', () => {
87102
const malformedParsedCollector = cloneDeep(parsedWorkingCollector);

packages/kbn-telemetry-tools/src/tools/extract_collectors.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('extractCollectors', () => {
3434
const programPaths = await getProgramPaths(configs[0]);
3535

3636
const results = [...extractCollectors(programPaths, tsConfig)];
37-
expect(results).toHaveLength(6);
37+
expect(results).toHaveLength(7);
3838
expect(results).toMatchSnapshot();
3939
});
4040
});

packages/kbn-telemetry-tools/src/tools/serializer.ts

+5
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ export function getDescriptor(node: ts.Node, program: ts.Program): Descriptor |
8484
}, {} as any);
8585
}
8686

87+
// If it's defined as signature { [key: string]: OtherInterface }
88+
if (ts.isIndexSignatureDeclaration(node) && node.type) {
89+
return { '@@INDEX@@': getDescriptor(node.type, program) };
90+
}
91+
8792
if (ts.SyntaxKind.FirstNode === node.kind) {
8893
return getDescriptor((node as any).right, program);
8994
}

packages/kbn-telemetry-tools/src/tools/utils.ts

+33-4
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ export function getVariableValue(node: ts.Node): string | Record<string, any> {
9898
return serializeObject(node);
9999
}
100100

101+
if (ts.isIdentifier(node)) {
102+
const declaration = getIdentifierDeclaration(node);
103+
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
104+
return getVariableValue(declaration.initializer);
105+
}
106+
// TODO: If this is another imported value from another file, we'll need to go fetch it like in getPropertyValue
107+
}
108+
101109
throw Error(`Unsuppored Node: cannot get value of node (${node.getText()}) of kind ${node.kind}`);
102110
}
103111

@@ -112,10 +120,11 @@ export function serializeObject(node: ts.Node) {
112120
if (typeof propertyName === 'undefined') {
113121
throw new Error(`Unable to get property name ${property.getText()}`);
114122
}
123+
const cleanPropertyName = propertyName.replace(/["']/g, '');
115124
if (ts.isPropertyAssignment(property)) {
116-
value[propertyName] = getVariableValue(property.initializer);
125+
value[cleanPropertyName] = getVariableValue(property.initializer);
117126
} else {
118-
value[propertyName] = getVariableValue(property);
127+
value[cleanPropertyName] = getVariableValue(property);
119128
}
120129
}
121130

@@ -222,9 +231,29 @@ export const flattenKeys = (obj: any, keyPath: any[] = []): any => {
222231
};
223232

224233
export function difference(actual: any, expected: any) {
225-
function changes(obj: any, base: any) {
234+
function changes(obj: { [key: string]: any }, base: { [key: string]: any }) {
226235
return transform(obj, function (result, value, key) {
227-
if (key && !isEqual(value, base[key])) {
236+
if (key && /@@INDEX@@/.test(`${key}`)) {
237+
// The type definition is an Index Signature, fuzzy searching for similar keys
238+
const regexp = new RegExp(`${key}`.replace(/@@INDEX@@/g, '(.+)?'));
239+
const keysInBase = Object.keys(base)
240+
.map((k) => {
241+
const match = k.match(regexp);
242+
return match && match[0];
243+
})
244+
.filter((s): s is string => !!s);
245+
246+
if (keysInBase.length === 0) {
247+
// Mark this key as wrong because we couldn't find any matching keys
248+
result[key] = value;
249+
}
250+
251+
keysInBase.forEach((k) => {
252+
if (!isEqual(value, base[k])) {
253+
result[k] = isObject(value) && isObject(base[k]) ? changes(value, base[k]) : value;
254+
}
255+
});
256+
} else if (key && !isEqual(value, base[key])) {
228257
result[key] = isObject(value) && isObject(base[key]) ? changes(value, base[key]) : value;
229258
}
230259
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
import { CollectorSet } from '../../plugins/usage_collection/server/collector';
20+
import { loggerMock } from '../../core/server/logging/logger.mock';
21+
22+
const { makeUsageCollector } = new CollectorSet({
23+
logger: loggerMock.create(),
24+
maximumWaitTimeForAllCollectorsInS: 0,
25+
});
26+
27+
interface Usage {
28+
[key: string]: {
29+
count_1?: number;
30+
count_2?: number;
31+
};
32+
}
33+
34+
export const myCollector = makeUsageCollector<Usage>({
35+
type: 'indexed_interface_with_not_matching_schema',
36+
isReady: () => true,
37+
fetch() {
38+
if (Math.random()) {
39+
return { something: { count_1: 1 } };
40+
}
41+
return { something: { count_2: 2 } };
42+
},
43+
schema: {
44+
something: {
45+
count_1: { type: 'long' }, // Intentionally missing count_2
46+
},
47+
},
48+
});

src/fixtures/telemetry_collectors/working_collector.ts

+9
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ interface Usage {
3535
my_objects: MyObject;
3636
my_array?: MyObject[];
3737
my_str_array?: string[];
38+
my_index_signature_prop?: {
39+
[key: string]: number;
40+
};
3841
}
3942

4043
const SOME_NUMBER: number = 123;
@@ -93,5 +96,11 @@ export const myCollector = makeUsageCollector<Usage>({
9396
type: { type: 'boolean' },
9497
},
9598
my_str_array: { type: 'keyword' },
99+
my_index_signature_prop: {
100+
count: { type: 'number' },
101+
avg: { type: 'number' },
102+
max: { type: 'number' },
103+
min: { type: 'number' },
104+
},
96105
},
97106
});

0 commit comments

Comments
 (0)