Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: unify logic for metrics collection when any command is executed #8

Merged
merged 8 commits into from
Jan 12, 2024
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
21 changes: 15 additions & 6 deletions src/base.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command } from '@oclif/core';
import { MetadataFromDocument, MetricMetadata, NewRelicSink, Recorder, Sink, StdOutSink } from '@smoya/asyncapi-adoption-metrics';
import { Parser } from '@asyncapi/parser';
import { Specification } from 'models/SpecificationFile';

class DiscardSink implements Sink {
async send() {
Expand All @@ -11,6 +12,14 @@ class DiscardSink implements Sink {
export default abstract class extends Command {
recorder = this.recorderFromEnv('asyncapi_adoption');
parser = new Parser();
metricsMetadata: MetricMetadata = {};
specFile: Specification | undefined;

async init(): Promise<void> {
await super.init();
const commandName : string = this.id || '';
await this.recordActionInvoked(commandName);
}

async catch(err: Error & { exitCode?: number; }): Promise<any> {
try {
Expand All @@ -36,7 +45,7 @@ export default abstract class extends Command {
}
}
}

const callable = async function(recorder: Recorder) {
await recorder.recordActionExecuted(action, metadata);
};
Expand All @@ -63,10 +72,10 @@ export default abstract class extends Command {
}
}

async init(): Promise<void> {
await super.init();
const commandName : string = this.id || '';
await this.recordActionInvoked(commandName);
async finally(error: Error | undefined): Promise<any> {
await super.finally(error);
this.metricsMetadata['success'] = error === undefined;
await this.recordActionExecuted(this.id as string, this.metricsMetadata, this.specFile?.text());
}

recorderFromEnv(prefix: string): Recorder {
Expand All @@ -82,7 +91,7 @@ export default abstract class extends Command {
break;
case 'production':
// NODE_ENV set to `production` in bin/run_bin, which is specified in 'bin' package.json section
sink = new NewRelicSink('eu01xx73a8521047150dd9414f6aedd2FFFFNRAL');
sink = new NewRelicSink(process.env.ASYNCAPI_METRICS_NEWRELIC_KEY || 'eu01xx73a8521047150dd9414f6aedd2FFFFNRAL');
this.warn('AsyncAPI anonymously tracks command executions to improve the specification and tools, ensuring no sensitive data reaches our servers. It aids in comprehending how AsyncAPI tools are used and adopted, facilitating ongoing improvements to our specifications and tools.\n\nTo disable tracking, set the "ASYNCAPI_METRICS" env variable to "false" when executing the command. For instance:\n\nASYNCAPI_METRICS=false asyncapi validate spec_file.yaml');
break;
}
Expand Down
19 changes: 15 additions & 4 deletions src/commands/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { promises } from 'fs';
import path from 'path';
import { Specification, load } from '../models/SpecificationFile';
import { Parser } from '@asyncapi/parser';
import { Document } from '@asyncapi/bundler/lib/document';

const { writeFile } = promises;

Expand Down Expand Up @@ -36,6 +37,8 @@ export default class Bundle extends Command {
const outputFormat = path.extname(argv[0]);
const AsyncAPIFiles = await this.loadFiles(argv);

this.metricsMetadata.files = AsyncAPIFiles.length;

const containsAsyncAPI3 = AsyncAPIFiles.filter((file) => {
return file.isAsyncAPI3();
});
Expand All @@ -53,6 +56,8 @@ export default class Bundle extends Command {
base: baseFile
}
);

await this.collectMetricsData(document);

if (!output) {
if (outputFormat === '.yaml' || outputFormat === '.yml') {
Expand All @@ -76,11 +81,17 @@ export default class Bundle extends Command {
}
this.log(`Check out your shiny new bundled files at ${output}`);
}
}

const result = await load(output);

// Metrics recording.
await this.recordActionExecuted(result.text(), {success: true, files: AsyncAPIFiles.length});
private async collectMetricsData(document: Document) {
try {
// We collect the metadata from the final output so it contains all the files
this.specFile = await load(new Specification(document.string()).text());
} catch (e: any) {
if (e instanceof Error) {
this.log(`Skipping submitting anonymous metrics due to the following error: ${e.name}: ${e.message}`);
}
}
}

async loadFiles(filepaths: string[]): Promise<Specification[]> {
Expand Down
32 changes: 7 additions & 25 deletions src/commands/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { ValidationError } from '../errors/validation-error';
import { load } from '../models/SpecificationFile';
import { SpecificationFileNotFound } from '../errors/specification-file';
import { convert } from '@asyncapi/converter';
import { MetadataFromDocument, MetricMetadata } from '@smoya/asyncapi-adoption-metrics';

import type { ConvertVersion } from '@asyncapi/converter';

// @ts-ignore
Expand All @@ -31,21 +29,21 @@ export default class Convert extends Command {
async run() {
const { args, flags } = await this.parse(Convert);
const filePath = args['spec-file'];
let specFile;
let convertedFile;
let convertedFileFormatted;

try {
// LOAD FILE
specFile = await load(filePath);
this.specFile = await load(filePath);
this.metricsMetadata.to_version = flags['target-version'];

// CONVERSION
convertedFile = convert(specFile.text(), flags['target-version'] as ConvertVersion);
convertedFile = convert(this.specFile.text(), flags['target-version'] as ConvertVersion);
if (convertedFile) {
if (specFile.getFilePath()) {
this.log(`File ${specFile.getFilePath()} successfully converted!`);
} else if (specFile.getFileURL()) {
this.log(`URL ${specFile.getFileURL()} successfully converted!`);
if (this.specFile.getFilePath()) {
this.log(`File ${this.specFile.getFilePath()} successfully converted!`);
} else if (this.specFile.getFileURL()) {
this.log(`URL ${this.specFile.getFileURL()} successfully converted!`);
}
}

Expand All @@ -70,21 +68,5 @@ export default class Convert extends Command {
this.error(err as Error);
}
}

// Metrics recording.
let metadata: MetricMetadata = {success: true, to_version: flags['target-version']};
try {
const {document} = await this.parser.parse(specFile.text());
if (document !== undefined) {
metadata = MetadataFromDocument(document, metadata);
metadata['from_version'] = document.version();
}
} catch (e: any) {
if (e instanceof Error) {
this.log(`Skipping submitting anonymous metrics due to the following error: ${e.name}: ${e.message}`);
}
}

await this.recordActionExecuted('convert', metadata);
}
}
7 changes: 3 additions & 4 deletions src/commands/generate/fromTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { isLocalTemplate, Watcher } from '../../utils/generator';
import { ValidationError } from '../../errors/validation-error';
import { GeneratorError } from '../../errors/generator-error';
import { Parser } from '@asyncapi/parser';

import type { Example } from '@oclif/core/lib/interfaces';

const red = (text: string) => `\x1b[31m${text}\x1b[0m`;
Expand Down Expand Up @@ -127,6 +126,9 @@ export default class Template extends Command {
disabledHooks: parsedFlags.disableHooks,
};
const asyncapiInput = (await load(asyncapi)) || (await load());

this.specFile = asyncapiInput;
this.metricsMetadata.template = template;

const watchTemplate = flags['watch'];
const genOption: any = {};
Expand All @@ -145,9 +147,6 @@ export default class Template extends Command {
const watcherHandler = this.watcherHandler(asyncapi, template, output, options, genOption);
await this.runWatchMode(asyncapi, template, output, watcherHandler);
}

// Metrics recording.
await this.recordActionExecuted('generate_from_template', {success: true, template}, asyncapiInput.text());
}

private parseFlags(disableHooks?: string[], params?: string[], mapBaseUrl?: string): ParsedFlags {
Expand Down
25 changes: 12 additions & 13 deletions src/commands/optimize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Flags } from '@oclif/core';
import { Optimizer, Output, Report, ReportElement } from '@asyncapi/optimizer';
import Command from '../base';
import { ValidationError } from '../errors/validation-error';
import { load, Specification } from '../models/SpecificationFile';
import { load } from '../models/SpecificationFile';
import * as inquirer from 'inquirer';
import chalk from 'chalk';
import { promises } from 'fs';
Expand All @@ -14,7 +14,7 @@ const { writeFile } = promises;
export enum Optimizations {
REMOVE_COMPONENTS='remove-components',
REUSE_COMPONENTS='reuse-components',
MOVE_TO_COMPONETS='move-to-components'
MOVE_TO_COMPONENTS='move-to-components'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

john-cena-deformed-gif

Copy link
Collaborator Author

@peter-rr peter-rr Jan 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah...it was there since a loooong time ago 🙈

}

export enum Outputs {
Expand Down Expand Up @@ -51,9 +51,9 @@ export default class Optimize extends Command {
async run() {
const { args, flags } = await this.parse(Optimize); //NOSONAR
const filePath = args['spec-file'];
let specFile: Specification;

try {
specFile = await load(filePath);
this.specFile = await load(filePath);
} catch (err) {
this.error(
new ValidationError({
Expand All @@ -63,14 +63,14 @@ export default class Optimize extends Command {
);
}

if (specFile.isAsyncAPI3()) {
if (this.specFile.isAsyncAPI3()) {
this.error('Optimize command does not support AsyncAPI v3 yet, please checkout https://github.com/asyncapi/optimizer/issues/168');
}

let optimizer: Optimizer;
let report: Report;
try {
optimizer = new Optimizer(specFile.text());
optimizer = new Optimizer(this.specFile.text());
report = await optimizer.getReport();
} catch (err) {
this.error(
Expand All @@ -84,8 +84,10 @@ export default class Optimize extends Command {
this.optimizations = flags.optimization as Optimizations[];
this.outputMethod = flags.output as Outputs;

this.metricsMetadata.optimizations = this.optimizations;

if (!(report.moveToComponents?.length || report.removeComponents?.length || report.reuseComponents?.length)) {
this.log(`No optimization has been applied since ${specFile.getFilePath() ?? specFile.getFileURL()} looks optimized!`);
this.log(`No optimization has been applied since ${this.specFile.getFilePath() ?? this.specFile.getFileURL()} looks optimized!`);
return;
}

Expand All @@ -96,12 +98,12 @@ export default class Optimize extends Command {

try {
const optimizedDocument = optimizer.getOptimizedDocument({rules: {
moveToComponents: this.optimizations.includes(Optimizations.MOVE_TO_COMPONETS),
moveToComponents: this.optimizations.includes(Optimizations.MOVE_TO_COMPONENTS),
removeComponents: this.optimizations.includes(Optimizations.REMOVE_COMPONENTS),
reuseComponents: this.optimizations.includes(Optimizations.REUSE_COMPONENTS)
}, output: Output.YAML});

const specPath = specFile.getFilePath();
const specPath = this.specFile.getFilePath();
let newPath = '';

if (specPath) {
Expand All @@ -127,9 +129,6 @@ export default class Optimize extends Command {
err: error
});
}

// Metrics recording.
await this.recordActionExecuted('optimize', {success: true, optimizations: this.optimizations}, specFile.text());
}

private showOptimizations(elements: ReportElement[] | undefined) {
Expand Down Expand Up @@ -159,7 +158,7 @@ export default class Optimize extends Command {
const totalMove = report.moveToComponents?.filter((e: ReportElement) => e.action === 'move').length;
this.log(`\n${chalk.green(totalMove)} components can be moved to the components sections.\nthe following changes will be made:`);
this.showOptimizations(report.moveToComponents);
choices.push({name: 'move to components section', value: Optimizations.MOVE_TO_COMPONETS});
choices.push({name: 'move to components section', value: Optimizations.MOVE_TO_COMPONENTS});
}
if (canRemove) {
const totalMove = report.removeComponents?.length;
Expand Down
10 changes: 4 additions & 6 deletions src/commands/validate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Flags } from '@oclif/core';

import Command from '../base';
import { validate, validationFlags } from '../parser';
import { load } from '../models/SpecificationFile';
Expand All @@ -24,14 +23,13 @@ export default class Validate extends Command {
const filePath = args['spec-file'];
const watchMode = flags.watch;

const specFile = await load(filePath);
this.specFile = await load(filePath);
if (watchMode) {
specWatcher({ spec: specFile, handler: this, handlerName: 'validate' });
specWatcher({ spec: this.specFile, handler: this, handlerName: 'validate' });
}

const result = await validate(this, specFile, flags);
const result = await validate(this, this.specFile, flags);

// Metrics recording.
await this.recordActionExecuted('validate', {success: true, validation_result: result}, specFile.text());
this.metricsMetadata.validation_result = result;
}
}
Loading