[Telemetry] Fix telemetry-tools TS parser for packages (#149819)

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Alejandro Fernández Haro <alejandro.haro@elastic.co>
Closes https://github.com/elastic/kibana/issues/139389
Closes https://github.com/elastic/kibana/issues/100544
This commit is contained in:
Ahmad Bamieh 2023-01-31 03:09:09 +02:00 committed by GitHub
parent dda650f91b
commit e5fa891d21
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 232 additions and 423 deletions

View file

@ -5,5 +5,10 @@
"exclude": [
"src/plugins/kibana_usage_collection/server/collectors/config_usage/register_config_usage_collector.ts"
]
},
{
"output": "src/plugins/telemetry/schema/kbn_packages.json",
"root": "packages/",
"exclude": []
}
]

View file

@ -10,7 +10,7 @@ import { SyntaxKind } from 'typescript';
import { ParsedUsageCollection } from '../ts_parser';
export const parsedEnumCollector: ParsedUsageCollection = [
'src/fixtures/telemetry_collectors/enum_collector.ts',
'packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/enum_collector.ts',
{
collectorName: 'my_enum_collector',
fetch: {

View file

@ -11,7 +11,7 @@ import { ParsedUsageCollection } from '../ts_parser';
export const parsedExternallyDefinedCollector: ParsedUsageCollection[] = [
[
'src/fixtures/telemetry_collectors/externally_defined_collector.ts',
'packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/externally_defined_collector.ts',
{
collectorName: 'from_variable_collector',
schema: {
@ -33,7 +33,7 @@ export const parsedExternallyDefinedCollector: ParsedUsageCollection[] = [
},
],
[
'src/fixtures/telemetry_collectors/externally_defined_collector.ts',
'packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/externally_defined_collector.ts',
{
collectorName: 'from_fn_collector',
schema: {

View file

@ -11,7 +11,7 @@ import { ParsedUsageCollection } from '../ts_parser';
export const parsedImportedInterfaceFromExport: ParsedUsageCollection[] = [
[
'src/fixtures/telemetry_collectors/imported_interface_from_export/index.ts',
'packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/imported_interface_from_export/index.ts',
{
collectorName: 'importing_from_export_collector',
schema: {

View file

@ -11,7 +11,7 @@ import { ParsedUsageCollection } from '../ts_parser';
export const parsedImportedSchemaCollector: ParsedUsageCollection[] = [
[
'src/fixtures/telemetry_collectors/imported_schema.ts',
'packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/imported_schema.ts',
{
collectorName: 'with_imported_schema',
schema: {

View file

@ -11,7 +11,7 @@ import { ParsedUsageCollection } from '../ts_parser';
export const parsedImportedUsageInterface: ParsedUsageCollection[] = [
[
'src/fixtures/telemetry_collectors/imported_usage_interface.ts',
'packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/imported_usage_interface.ts',
{
collectorName: 'imported_usage_interface_collector',
schema: {

View file

@ -10,7 +10,7 @@ import { SyntaxKind } from 'typescript';
import { ParsedUsageCollection } from '../ts_parser';
export const parsedIndexedInterfaceWithNoMatchingSchema: ParsedUsageCollection = [
'src/fixtures/telemetry_collectors/indexed_interface_with_not_matching_schema.ts',
'packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/indexed_interface_with_not_matching_schema.ts',
{
collectorName: 'indexed_interface_with_not_matching_schema',
schema: {

View file

@ -10,7 +10,7 @@ import { SyntaxKind } from 'typescript';
import { ParsedUsageCollection } from '../ts_parser';
export const parsedNestedCollector: ParsedUsageCollection = [
'src/fixtures/telemetry_collectors/nested_collector.ts',
'packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/nested_collector.ts',
{
collectorName: 'my_nested_collector',
schema: {

View file

@ -10,7 +10,7 @@ import { SyntaxKind } from 'typescript';
import { ParsedUsageCollection } from '../ts_parser';
export const parsedSchemaDefinedWithSpreadsCollector: ParsedUsageCollection = [
'src/fixtures/telemetry_collectors/schema_defined_with_spreads_collector.ts',
'packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/schema_defined_with_spreads_collector.ts',
{
collectorName: 'schema_defined_with_spreads',
schema: {

View file

@ -11,7 +11,7 @@ import { ParsedUsageCollection } from '../ts_parser';
export const parsedStatsCollector: ParsedUsageCollection[] = [
[
'src/fixtures/telemetry_collectors/stats_collector.ts',
'packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/stats_collector.ts',
{
collectorName: 'my_stats_collector_with_schema',
schema: {

View file

@ -10,7 +10,7 @@ import { SyntaxKind } from 'typescript';
import { ParsedUsageCollection } from '../ts_parser';
export const parsedWorkingCollector: ParsedUsageCollection = [
'src/fixtures/telemetry_collectors/working_collector.ts',
'packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/working_collector.ts',
{
collectorName: 'my_working_collector',
schema: {

View file

@ -10,7 +10,7 @@ import { SyntaxKind } from 'typescript';
import { ParsedUsageCollection } from '../ts_parser';
export const parsedCollectorWithDescription: ParsedUsageCollection = [
'src/fixtures/telemetry_collectors/working_collector_with_description.ts',
'packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/working_collector_with_description.ts',
{
collectorName: 'my_working_collector_with_description',
schema: {

View file

@ -7,7 +7,7 @@
*/
import moment, { Moment } from 'moment';
import { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server';
import type { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server';
export interface Usage {
locale: string;

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
const { makeUsageCollector } = createUsageCollectionSetupMock();

View file

@ -6,8 +6,9 @@
* Side Public License, v 1.
*/
import type { UsageCollectorOptions } from '@kbn/usage-collection-plugin/server';
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
import type { UsageCollectorOptions } from '@kbn/usage-collection-plugin/server';
const collectorSet = createUsageCollectionSetupMock();

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { getUsageCollector } from './get_usage_collector';

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
import type { Usage } from './types';

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
import { externallyDefinedSchema } from './constants';

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
import { Usage } from './constants';

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
const { makeUsageCollector } = createUsageCollectionSetupMock();

View file

@ -6,8 +6,9 @@
* Side Public License, v 1.
*/
import type { Collector } from '@kbn/usage-collection-plugin/server';
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
import type { Collector } from '@kbn/usage-collection-plugin/server';
const collectorSet = createUsageCollectionSetupMock();

View file

@ -6,8 +6,9 @@
* Side Public License, v 1.
*/
import type { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server';
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
import type { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server';
const { makeUsageCollector } = createUsageCollectionSetupMock();

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
const { makeStatsCollector } = createUsageCollectionSetupMock();

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
const { makeUsageCollector } = createUsageCollectionSetupMock();

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
const { makeUsageCollector } = createUsageCollectionSetupMock();

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
const { makeUsageCollector } = createUsageCollectionSetupMock();

View file

@ -1,11 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`parseUsageCollection throws when \`makeUsageCollector\` argument is a function call 1`] = `
"Error extracting collector in src/fixtures/telemetry_collectors/externally_defined_usage_collector/index.ts
"Error extracting collector in packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/externally_defined_usage_collector/index.ts
Error: makeUsageCollector argument must be an object."
`;
exports[`parseUsageCollection throws when mapping fields is not defined 1`] = `
"Error extracting collector in src/fixtures/telemetry_collectors/unmapped_collector.ts
"Error extracting collector in packages/kbn-telemetry-tools/src/tools/__fixture__/telemetry_collectors/unmapped_collector.ts
Error: usageCollector.schema must be defined."
`;

View file

@ -7,18 +7,16 @@
*/
import { cloneDeep } from 'lodash';
import * as ts from 'typescript';
import ts from 'typescript';
import { parsedWorkingCollector } from './__fixture__/parsed_working_collector';
import { parsedIndexedInterfaceWithNoMatchingSchema } from './__fixture__/parsed_indexed_interface_with_not_matching_schema';
import { checkCompatibleTypeDescriptor, checkMatchingMapping } from './check_collector_integrity';
import * as path from 'path';
import { readFile } from 'fs';
import { promisify } from 'util';
const read = promisify(readFile);
import { readFile } from 'fs/promises';
async function parseJsonFile(relativePath: string) {
const schemaPath = path.resolve(__dirname, '__fixture__', relativePath);
const fileContent = await read(schemaPath, 'utf8');
const fileContent = await readFile(schemaPath, 'utf8');
return JSON.parse(fileContent);
}

View file

@ -7,7 +7,6 @@
*/
import * as path from 'path';
import { REPO_ROOT } from '@kbn/repo-info';
import { parseTelemetryRC } from './config';
describe('parseTelemetryRC', () => {
@ -17,7 +16,7 @@ describe('parseTelemetryRC', () => {
});
it('returns parsed rc file', async () => {
const configRoot = path.resolve(REPO_ROOT, 'src/fixtures/telemetry_collectors');
const configRoot = path.resolve(__dirname, '__fixture__', 'telemetry_collectors');
const config = await parseTelemetryRC(configRoot);
expect(config).toStrictEqual([
{

View file

@ -7,7 +7,7 @@
*/
import * as path from 'path';
import { readFileAsync } from './utils';
import { readFile } from 'fs/promises';
import { TELEMETRY_RC } from './constants';
export interface TelemetryRC {
@ -22,7 +22,7 @@ export async function readRcFile(rcRoot: string) {
}
const rcFile = path.resolve(rcRoot, TELEMETRY_RC);
const configString = await readFileAsync(rcFile, 'utf8');
const configString = await readFile(rcFile, 'utf8');
return JSON.parse(configString);
}

View file

@ -6,16 +6,15 @@
* Side Public License, v 1.
*/
import * as ts from 'typescript';
import ts from 'typescript';
import * as path from 'path';
import { REPO_ROOT } from '@kbn/repo-info';
import { extractCollectors, getProgramPaths } from './extract_collectors';
import { parseTelemetryRC } from './config';
import { allExtractedCollectors } from './__fixture__/all_extracted_collectors';
describe('extractCollectors', () => {
it('extracts collectors given rc file', async () => {
const configRoot = path.join(REPO_ROOT, 'src/fixtures/telemetry_collectors');
const configRoot = path.resolve(__dirname, '__fixture__', 'telemetry_collectors');
const tsConfig = ts.findConfigFile('./', ts.sys.fileExists, 'tsconfig.json');
if (!tsConfig) {
throw new Error('Could not find a valid tsconfig.json.');

View file

@ -7,11 +7,10 @@
*/
import globby from 'globby';
import * as ts from 'typescript';
import * as path from 'path';
import { parseUsageCollection } from './ts_parser';
import { TelemetryRC } from './config';
import { compilerHost } from './compiler_host';
import { createKibanaProgram, getAllSourceFiles } from './ts_program';
export async function getProgramPaths({
root,
@ -52,15 +51,8 @@ export async function getProgramPaths({
}
export function* extractCollectors(fullPaths: string[], tsConfig: any) {
const program = ts.createProgram(fullPaths, tsConfig, compilerHost);
program.getTypeChecker();
const sourceFiles = fullPaths.map((fullPath) => {
const sourceFile = program.getSourceFile(fullPath);
if (!sourceFile) {
throw Error(`Unable to get sourceFile ${fullPath}.`);
}
return sourceFile;
});
const program = createKibanaProgram(fullPaths, tsConfig);
const sourceFiles = getAllSourceFiles(fullPaths, program);
for (const sourceFile of sourceFiles) {
yield* parseUsageCollection(sourceFile, program);

View file

@ -10,13 +10,11 @@ import { generateMapping } from './manage_schema';
import { parsedWorkingCollector } from './__fixture__/parsed_working_collector';
import { parsedCollectorWithDescription } from './__fixture__/parsed_working_collector_with_description';
import * as path from 'path';
import { readFile } from 'fs';
import { promisify } from 'util';
const read = promisify(readFile);
import { readFile } from 'fs/promises';
async function parseJsonFile(relativePath: string) {
const schemaPath = path.resolve(__dirname, '__fixture__', relativePath);
const fileContent = await read(schemaPath, 'utf8');
const fileContent = await readFile(schemaPath, 'utf8');
return JSON.parse(fileContent);
}

View file

@ -6,36 +6,16 @@
* Side Public License, v 1.
*/
import * as ts from 'typescript';
import * as path from 'path';
import { REPO_ROOT } from '@kbn/repo-info';
import ts from 'typescript';
import { getDescriptor, TelemetryKinds } from './serializer';
import { traverseNodes } from './ts_parser';
import { compilerHost } from './compiler_host';
export function loadFixtureProgram(fixtureName: string) {
const fixturePath = path.resolve(
REPO_ROOT,
`src/fixtures/telemetry_collectors/${fixtureName}.ts`
);
const tsConfig = ts.findConfigFile('./', ts.sys.fileExists, 'tsconfig.json');
if (!tsConfig) {
throw new Error('Could not find a valid tsconfig.json.');
}
const program = ts.createProgram([fixturePath], tsConfig as any, compilerHost);
const checker = program.getTypeChecker();
const sourceFile = program.getSourceFile(fixturePath);
if (!sourceFile) {
throw Error('sourceFile is undefined!');
}
return { program, checker, sourceFile };
}
import { loadFixtureProgram } from './test_utils';
describe('getDescriptor', () => {
const usageInterfaces = new Map<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration>();
let tsProgram: ts.Program;
beforeAll(() => {
const { program, sourceFile } = loadFixtureProgram('constants');
const { program, sourceFile } = loadFixtureProgram('telemetry_collectors/constants.ts');
tsProgram = program;
for (const node of traverseNodes(sourceFile)) {
if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) {

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import * as ts from 'typescript';
import ts from 'typescript';
import { uniqBy, pick, omit } from 'lodash';
import {
getResolvedModuleSourceFile,

View file

@ -7,15 +7,15 @@
*/
import * as path from 'path';
import { readFile } from 'fs/promises';
import { TaskContext } from './task_context';
import { checkMatchingMapping } from '../check_collector_integrity';
import { readFileAsync } from '../utils';
export function checkMatchingSchemasTask({ roots }: TaskContext, throwOnDiff: boolean) {
return roots.map((root) => ({
task: async () => {
const fullPath = path.resolve(process.cwd(), root.config.output);
const esMappingString = await readFileAsync(fullPath, 'utf-8');
const esMappingString = await readFile(fullPath, 'utf-8');
const esMapping = JSON.parse(esMappingString);
if (root.parsedCollections) {

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import * as ts from 'typescript';
import ts from 'typescript';
import * as path from 'path';
import { TaskContext } from './task_context';
import { extractCollectors, getProgramPaths } from '../extract_collectors';

View file

@ -7,7 +7,7 @@
*/
import * as path from 'path';
import { writeFileAsync } from '../utils';
import { writeFile } from 'fs/promises';
import { TaskContext } from './task_context';
export function writeToFileTask({ roots }: TaskContext) {
@ -22,7 +22,7 @@ export function writeToFileTask({ roots }: TaskContext) {
})
);
const serializedMapping = JSON.stringify(root.mapping, null, 2).concat('\n');
await writeFileAsync(fullPath, serializedMapping);
await writeFile(fullPath, serializedMapping);
}
},
title: `Writing mapping for ${root.config.root}`,

View file

@ -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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import ts from 'typescript';
import * as path from 'path';
import { createKibanaProgram } from './ts_program';
export function loadFixtureProgram(fixtureFile: string, basepath = __dirname) {
const fixturePath = path.resolve(basepath, '__fixture__', fixtureFile);
const tsConfig = ts.findConfigFile('./', ts.sys.fileExists, 'tsconfig.json');
if (!tsConfig) {
throw new Error('Could not find a valid tsconfig.json.');
}
const program = createKibanaProgram([fixturePath], tsConfig);
const checker = program.getTypeChecker();
const sourceFile = program.getSourceFile(fixturePath);
if (!sourceFile) {
throw Error('sourceFile is undefined!');
}
return { program, checker, sourceFile };
}

View file

@ -7,10 +7,6 @@
*/
import { parseUsageCollection } from './ts_parser';
import * as ts from 'typescript';
import * as path from 'path';
import { REPO_ROOT } from '@kbn/repo-info';
import { compilerHost } from './compiler_host';
import { parsedWorkingCollector } from './__fixture__/parsed_working_collector';
import { parsedNestedCollector } from './__fixture__/parsed_nested_collector';
import { parsedExternallyDefinedCollector } from './__fixture__/parsed_externally_defined_collector';
@ -19,21 +15,7 @@ import { parsedImportedSchemaCollector } from './__fixture__/parsed_imported_sch
import { parsedSchemaDefinedWithSpreadsCollector } from './__fixture__/parsed_schema_defined_with_spreads_collector';
import { parsedStatsCollector } from './__fixture__/parsed_stats_collector';
import { parsedImportedInterfaceFromExport } from './__fixture__/parsed_imported_interface_from_export';
export function loadFixtureProgram(fixtureName: string) {
const fixturePath = path.resolve(REPO_ROOT, `src/fixtures/telemetry_collectors/${fixtureName}`);
const tsConfig = ts.findConfigFile('./', ts.sys.fileExists, 'tsconfig.json');
if (!tsConfig) {
throw new Error('Could not find a valid tsconfig.json.');
}
const program = ts.createProgram([fixturePath], tsConfig as any, compilerHost);
const checker = program.getTypeChecker();
const sourceFile = program.getSourceFile(fixturePath);
if (!sourceFile) {
throw Error('sourceFile is undefined!');
}
return { program, checker, sourceFile };
}
import { loadFixtureProgram } from './test_utils';
describe('parseUsageCollection', () => {
it.todo('throws when a function is returned from fetch');
@ -41,66 +23,78 @@ describe('parseUsageCollection', () => {
it('throws when `makeUsageCollector` argument is a function call', () => {
const { program, sourceFile } = loadFixtureProgram(
'externally_defined_usage_collector/index.ts'
'telemetry_collectors/externally_defined_usage_collector/index.ts'
);
expect(() => [...parseUsageCollection(sourceFile, program)]).toThrowErrorMatchingSnapshot();
});
it('throws when mapping fields is not defined', () => {
const { program, sourceFile } = loadFixtureProgram('unmapped_collector.ts');
const { program, sourceFile } = loadFixtureProgram(
'telemetry_collectors/unmapped_collector.ts'
);
expect(() => [...parseUsageCollection(sourceFile, program)]).toThrowErrorMatchingSnapshot();
});
it('parses root level defined collector', () => {
const { program, sourceFile } = loadFixtureProgram('working_collector.ts');
const { program, sourceFile } = loadFixtureProgram('telemetry_collectors/working_collector.ts');
const result = [...parseUsageCollection(sourceFile, program)];
expect(result).toEqual([parsedWorkingCollector]);
});
it('parses collector with schema defined as union of spreads', () => {
const { program, sourceFile } = loadFixtureProgram('schema_defined_with_spreads_collector.ts');
const { program, sourceFile } = loadFixtureProgram(
'telemetry_collectors/schema_defined_with_spreads_collector.ts'
);
const result = [...parseUsageCollection(sourceFile, program)];
expect(result).toEqual([parsedSchemaDefinedWithSpreadsCollector]);
});
it('parses nested collectors', () => {
const { program, sourceFile } = loadFixtureProgram('nested_collector.ts');
const { program, sourceFile } = loadFixtureProgram('telemetry_collectors/nested_collector.ts');
const result = [...parseUsageCollection(sourceFile, program)];
expect(result).toEqual([parsedNestedCollector]);
});
it('parses imported schema property', () => {
const { program, sourceFile } = loadFixtureProgram('imported_schema.ts');
const { program, sourceFile } = loadFixtureProgram('telemetry_collectors/imported_schema.ts');
const result = [...parseUsageCollection(sourceFile, program)];
expect(result).toEqual(parsedImportedSchemaCollector);
});
it('parses externally defined collectors', () => {
const { program, sourceFile } = loadFixtureProgram('externally_defined_collector.ts');
const { program, sourceFile } = loadFixtureProgram(
'telemetry_collectors/externally_defined_collector.ts'
);
const result = [...parseUsageCollection(sourceFile, program)];
expect(result).toEqual(parsedExternallyDefinedCollector);
});
it('parses imported Usage interface', () => {
const { program, sourceFile } = loadFixtureProgram('imported_usage_interface.ts');
const { program, sourceFile } = loadFixtureProgram(
'telemetry_collectors/imported_usage_interface.ts'
);
const result = [...parseUsageCollection(sourceFile, program)];
expect(result).toEqual(parsedImportedUsageInterface);
});
it('parses stats collectors, discarding those without schemas', () => {
const { program, sourceFile } = loadFixtureProgram('stats_collector.ts');
const { program, sourceFile } = loadFixtureProgram('telemetry_collectors/stats_collector.ts');
const result = [...parseUsageCollection(sourceFile, program)];
expect(result).toEqual(parsedStatsCollector);
});
it('follows `export { Usage } from "./path"` expressions', () => {
const { program, sourceFile } = loadFixtureProgram('imported_interface_from_export/index.ts');
const { program, sourceFile } = loadFixtureProgram(
'telemetry_collectors/imported_interface_from_export/index.ts'
);
const result = [...parseUsageCollection(sourceFile, program)];
expect(result).toEqual(parsedImportedInterfaceFromExport);
});
it('skips files that do not define a collector', () => {
const { program, sourceFile } = loadFixtureProgram('file_with_no_collector.ts');
const { program, sourceFile } = loadFixtureProgram(
'telemetry_collectors/file_with_no_collector.ts'
);
const result = [...parseUsageCollection(sourceFile, program)];
expect(result).toEqual([]);
});

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import * as ts from 'typescript';
import ts from 'typescript';
import { createFailError } from '@kbn/dev-cli-errors';
import * as path from 'path';
import { getProperty, getPropertyValue } from './utils';

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
const { makeUsageCollector } = createUsageCollectionSetupMock();
interface Usage {
locale?: string;
}
export const myCollector = makeUsageCollector<Usage>({
type: 'with_kbn_package_import',
isReady: () => true,
schema: {
locale: {
type: 'keyword',
},
},
fetch(): Usage {
return {
locale: 'en',
};
},
});

View file

@ -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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { getAllSourceFiles, createKibanaProgram } from './ts_program';

View file

@ -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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { parseUsageCollection } from '../ts_parser';
import { loadFixtureProgram } from '../test_utils';
describe('createKibanaProgram', () => {
it('parses files with @kbn/* imports', () => {
const { program, sourceFile } = loadFixtureProgram('with_kbn_package_import.ts', __dirname);
expect([...parseUsageCollection(sourceFile, program)]).toMatchInlineSnapshot(`
Array [
Array [
"packages/kbn-telemetry-tools/src/tools/ts_program/__fixture__/with_kbn_package_import.ts",
Object {
"collectorName": "with_kbn_package_import",
"fetch": Object {
"typeDescriptor": Object {
"locale": Object {
"kind": 149,
"type": "StringKeyword",
},
},
"typeName": "Usage",
},
"schema": Object {
"value": Object {
"locale": Object {
"type": "keyword",
},
},
},
},
],
]
`);
});
});

View file

@ -6,14 +6,13 @@
* Side Public License, v 1.
*/
import Path from 'path';
import * as path from 'path';
import ts from 'typescript';
import { REPO_ROOT } from '@kbn/repo-info';
import { ImportResolver } from '@kbn/import-resolver';
function readTsConfigFile(path: string) {
const json = ts.readConfigFile(path, ts.sys.readFile);
function readTsConfigFile(configPath: string) {
const json = ts.readConfigFile(configPath, ts.sys.readFile);
if (json.error) {
throw new Error(`Unable to load tsconfig file: ${json.error.messageText}`);
@ -22,15 +21,19 @@ function readTsConfigFile(path: string) {
return json.config;
}
function loadTsConfigFile(path: string) {
return ts.parseJsonConfigFileContent(readTsConfigFile(path) ?? {}, ts.sys, Path.dirname(path));
function loadTsConfigFile(configPath: string) {
return ts.parseJsonConfigFileContent(
readTsConfigFile(configPath) ?? {},
ts.sys,
path.dirname(configPath)
);
}
const baseTsConfig = loadTsConfigFile(Path.resolve(REPO_ROOT, 'tsconfig.base.json'));
const baseTsConfig = loadTsConfigFile(path.resolve(REPO_ROOT, 'tsconfig.base.json'));
const resolver = ImportResolver.create(REPO_ROOT);
function isTsCompatible(path: string) {
const extname = Path.extname(path);
function isTsCompatible(configPath: string) {
const extname = path.extname(configPath);
return extname === '.ts' || extname === '.tsx' || extname === '.js';
}
@ -38,7 +41,7 @@ export const compilerHost: ts.CompilerHost = {
...ts.createCompilerHost(baseTsConfig.options),
resolveModuleNames(moduleNames, sourceFilePath) {
const dirname = Path.dirname(sourceFilePath);
const dirname = path.dirname(sourceFilePath);
const results: Array<ts.ResolvedModule | undefined> = [];
@ -57,3 +60,20 @@ export const compilerHost: ts.CompilerHost = {
return results;
},
};
export function getAllSourceFiles(fullPaths: string[], program: ts.Program): ts.SourceFile[] {
return fullPaths.map((fullPath) => {
const sourceFile = program.getSourceFile(fullPath);
if (!sourceFile) {
throw Error(`Unable to get sourceFile ${fullPath}.`);
}
return sourceFile;
});
}
export function createKibanaProgram(fullPaths: string[], tsConfig: any): ts.Program {
const program = ts.createProgram(fullPaths, tsConfig, compilerHost);
program.getTypeChecker();
return program;
}

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import * as ts from 'typescript';
import ts from 'typescript';
import {
pick,
pickBy,
@ -20,14 +20,9 @@ import {
isEqual,
} from 'lodash';
import * as path from 'path';
import { readFile, writeFile } from 'fs';
import { promisify } from 'util';
import normalize from 'normalize-path';
import { Optional } from '@kbn/utility-types';
export const readFileAsync = promisify(readFile);
export const writeFileAsync = promisify(writeFile);
export function isPropertyWithKey(property: ts.Node, identifierName: string) {
if (ts.isPropertyAssignment(property) || ts.isMethodDeclaration(property)) {
if (ts.isIdentifier(property.name)) {

View file

@ -17,6 +17,7 @@
"@kbn/dev-cli-errors",
"@kbn/repo-info",
"@kbn/import-resolver",
"@kbn/usage-collection-plugin",
],
"exclude": [
"target/**/*",

View file

@ -1,15 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
},
"include": [
"telemetry_collectors/**/*",
],
"kbn_references": [
"@kbn/usage-collection-plugin",
],
"exclude": [
"target/**/*",
]
}

View file

@ -8,7 +8,7 @@
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { CoreUsageDataStart } from '@kbn/core/server';
import { CoreUsageData } from './core_usage_data';
import { CoreUsageData } from '@kbn/core-usage-data-server';
export function getCoreUsageCollector(
usageCollection: UsageCollectionSetup,

View file

@ -1,286 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
// duplicate of the types from @kbn/core-usage-data-server
// required until https://github.com/elastic/kibana/issues/139389 is addressed
export interface CoreUsageData extends CoreUsageStats {
config: CoreConfigUsageData;
services: CoreServicesUsageData;
environment: CoreEnvironmentUsageData;
}
export interface CoreUsageStats {
// Saved Objects Client APIs
'apiCalls.savedObjectsBulkCreate.total'?: number;
'apiCalls.savedObjectsBulkCreate.namespace.default.total'?: number;
'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsBulkCreate.namespace.custom.total'?: number;
'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsBulkGet.total'?: number;
'apiCalls.savedObjectsBulkGet.namespace.default.total'?: number;
'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsBulkGet.namespace.custom.total'?: number;
'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsBulkResolve.total'?: number;
'apiCalls.savedObjectsBulkResolve.namespace.default.total'?: number;
'apiCalls.savedObjectsBulkResolve.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsBulkResolve.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsBulkResolve.namespace.custom.total'?: number;
'apiCalls.savedObjectsBulkResolve.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsBulkResolve.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsBulkDelete.total'?: number;
'apiCalls.savedObjectsBulkDelete.namespace.default.total'?: number;
'apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsBulkDelete.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsBulkDelete.namespace.custom.total'?: number;
'apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsBulkDelete.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsBulkUpdate.total'?: number;
'apiCalls.savedObjectsBulkUpdate.namespace.default.total'?: number;
'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsBulkUpdate.namespace.custom.total'?: number;
'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsCreate.total'?: number;
'apiCalls.savedObjectsCreate.namespace.default.total'?: number;
'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsCreate.namespace.custom.total'?: number;
'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsDelete.total'?: number;
'apiCalls.savedObjectsDelete.namespace.default.total'?: number;
'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsDelete.namespace.custom.total'?: number;
'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsFind.total'?: number;
'apiCalls.savedObjectsFind.namespace.default.total'?: number;
'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsFind.namespace.custom.total'?: number;
'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsGet.total'?: number;
'apiCalls.savedObjectsGet.namespace.default.total'?: number;
'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsGet.namespace.custom.total'?: number;
'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsResolve.total'?: number;
'apiCalls.savedObjectsResolve.namespace.default.total'?: number;
'apiCalls.savedObjectsResolve.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsResolve.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsResolve.namespace.custom.total'?: number;
'apiCalls.savedObjectsResolve.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsResolve.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsUpdate.total'?: number;
'apiCalls.savedObjectsUpdate.namespace.default.total'?: number;
'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsUpdate.namespace.custom.total'?: number;
'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.no'?: number;
// Saved Objects Management APIs
'apiCalls.savedObjectsImport.total'?: number;
'apiCalls.savedObjectsImport.namespace.default.total'?: number;
'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsImport.namespace.custom.total'?: number;
'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsImport.createNewCopiesEnabled.yes'?: number;
'apiCalls.savedObjectsImport.createNewCopiesEnabled.no'?: number;
'apiCalls.savedObjectsImport.overwriteEnabled.yes'?: number;
'apiCalls.savedObjectsImport.overwriteEnabled.no'?: number;
'apiCalls.savedObjectsResolveImportErrors.total'?: number;
'apiCalls.savedObjectsResolveImportErrors.namespace.default.total'?: number;
'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsResolveImportErrors.namespace.custom.total'?: number;
'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.yes'?: number;
'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.no'?: number;
'apiCalls.savedObjectsExport.total'?: number;
'apiCalls.savedObjectsExport.namespace.default.total'?: number;
'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.no'?: number;
'apiCalls.savedObjectsExport.namespace.custom.total'?: number;
'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.savedObjectsExport.allTypesSelected.yes'?: number;
'apiCalls.savedObjectsExport.allTypesSelected.no'?: number;
// Legacy Dashboard Import/Export API
'apiCalls.legacyDashboardExport.total'?: number;
'apiCalls.legacyDashboardExport.namespace.default.total'?: number;
'apiCalls.legacyDashboardExport.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.legacyDashboardExport.namespace.default.kibanaRequest.no'?: number;
'apiCalls.legacyDashboardExport.namespace.custom.total'?: number;
'apiCalls.legacyDashboardExport.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.legacyDashboardExport.namespace.custom.kibanaRequest.no'?: number;
'apiCalls.legacyDashboardImport.total'?: number;
'apiCalls.legacyDashboardImport.namespace.default.total'?: number;
'apiCalls.legacyDashboardImport.namespace.default.kibanaRequest.yes'?: number;
'apiCalls.legacyDashboardImport.namespace.default.kibanaRequest.no'?: number;
'apiCalls.legacyDashboardImport.namespace.custom.total'?: number;
'apiCalls.legacyDashboardImport.namespace.custom.kibanaRequest.yes'?: number;
'apiCalls.legacyDashboardImport.namespace.custom.kibanaRequest.no'?: number;
// Saved Objects Repository counters
'savedObjectsRepository.resolvedOutcome.exactMatch'?: number;
'savedObjectsRepository.resolvedOutcome.aliasMatch'?: number;
'savedObjectsRepository.resolvedOutcome.conflict'?: number;
'savedObjectsRepository.resolvedOutcome.notFound'?: number;
'savedObjectsRepository.resolvedOutcome.total'?: number;
}
export interface CoreServicesUsageData {
savedObjects: {
// scripts/telemetry_check.js does not support parsing Array<{...}> types
// so we have to disable eslint here and use {...}[]
// eslint-disable-next-line @typescript-eslint/array-type
indices: {
alias: string;
docsCount: number;
docsDeleted: number;
storeSizeBytes: number;
primaryStoreSizeBytes: number;
savedObjectsDocsCount: number;
}[];
legacyUrlAliases: {
activeCount: number;
inactiveCount: number;
disabledCount: number;
totalCount: number;
};
};
}
/**
* Usage data on this Kibana node's runtime environment.
* @public
*/
export interface CoreEnvironmentUsageData {
memory: {
heapTotalBytes: number;
heapUsedBytes: number;
/** V8 heap size limit */
heapSizeLimit: number;
};
}
/**
* Usage data on this cluster's configuration of Core features
* @public
*/
export interface CoreConfigUsageData {
elasticsearch: {
sniffOnStart: boolean;
sniffIntervalMs?: number;
sniffOnConnectionFault: boolean;
numberOfHostsConfigured: number;
requestHeadersWhitelistConfigured: boolean;
customHeadersConfigured: boolean;
shardTimeoutMs: number;
requestTimeoutMs: number;
pingTimeoutMs: number;
logQueries: boolean;
ssl: {
verificationMode: 'none' | 'certificate' | 'full';
certificateAuthoritiesConfigured: boolean;
certificateConfigured: boolean;
keyConfigured: boolean;
keystoreConfigured: boolean;
truststoreConfigured: boolean;
alwaysPresentCertificate: boolean;
};
apiVersion: string;
healthCheckDelayMs: number;
principal:
| 'elastic_user'
| 'kibana_user'
| 'kibana_system_user'
| 'other_user'
| 'kibana_service_account'
| 'unknown';
};
http: {
basePathConfigured: boolean;
maxPayloadInBytes: number;
rewriteBasePath: boolean;
keepaliveTimeout: number;
socketTimeout: number;
compression: {
enabled: boolean;
referrerWhitelistConfigured: boolean;
};
xsrf: {
disableProtection: boolean;
allowlistConfigured: boolean;
};
requestId: {
allowFromAnyIp: boolean;
ipAllowlistConfigured: boolean;
};
ssl: {
certificateAuthoritiesConfigured: boolean;
certificateConfigured: boolean;
cipherSuites: string[];
keyConfigured: boolean;
keystoreConfigured: boolean;
truststoreConfigured: boolean;
redirectHttpFromPortConfigured: boolean;
supportedProtocols: string[];
clientAuthentication: 'none' | 'optional' | 'required';
};
securityResponseHeaders: {
strictTransportSecurity: string;
xContentTypeOptions: string;
referrerPolicy: string;
permissionsPolicyConfigured: boolean;
disableEmbedding: boolean;
crossOriginOpenerPolicy: string;
};
};
logging: {
appendersTypesUsed: string[];
loggersConfiguredCount: number;
};
// plugins: {
// /** list of built-in plugins that are disabled */
// firstPartyDisabled: string[];
// /** list of third-party plugins that are installed and enabled */
// thirdParty: string[];
// };
savedObjects: {
customIndex: boolean;
maxImportPayloadBytes: number;
maxImportExportSize: number;
};
// uiSettings: {
// overridesCount: number;
// };
deprecatedKeys: {
set: string[];
unset: string[];
};
}

View file

@ -17,6 +17,7 @@
"@kbn/i18n",
"@kbn/logging",
"@kbn/core-test-helpers-kbn-server",
"@kbn/core-usage-data-server",
],
"exclude": [
"target/**/*",

View file

@ -0,0 +1,4 @@
{
"properties": {
}
}