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

feat: add POC for measuring adoption #859

Merged
merged 102 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
9952df2
feat: add POC for measuring adoption
smoya Oct 22, 2023
07c7866
Update convert command
peter-rr Nov 6, 2023
e9e79f6
Add metrics recording to some commands
peter-rr Oct 31, 2023
4c7839d
feat: send metrics to NR only when installed as NPM binary (#2)
smoya Nov 23, 2023
94d6e03
feat: add new metric recording for action invocation (#3)
peter-rr Nov 23, 2023
17e9a5f
chore: refactor metrics recording so it gets unified (#4)
smoya Nov 23, 2023
ee6f3be
Merge branch 'master' into feat/adoptionMetrics
smoya Nov 23, 2023
0bb0791
remove unused hook
smoya Nov 23, 2023
3ce1520
add missing execute metrics
smoya Nov 23, 2023
2fd9762
fix metrics func callable
smoya Nov 23, 2023
03eaf22
chore: unify logic for metrics collection when any command is invoked…
peter-rr Dec 12, 2023
b99c19e
Merge branch 'master' into feat/adoptionMetrics
smoya Dec 12, 2023
4f81da5
Merge branch 'master' into feat/adoptionMetrics
smoya Dec 12, 2023
ed74a95
update @smoya/asyncapi-adoption-metrics
smoya Dec 13, 2023
ac30d00
Merge branch 'master' into feat/adoptionMetrics
smoya Dec 13, 2023
8f3e48a
fix: update @smoya/multi-parser to v5.0.1
smoya Dec 13, 2023
b10f763
fix: update @asyncapi/parser
smoya Dec 13, 2023
cbd1c51
chore: remove unused imports
smoya Dec 13, 2023
32c822c
Solve some bugs reported by SonarCloud (#7)
peter-rr Dec 13, 2023
e855ab3
Merge branch 'master' into feat/adoptionMetrics
smoya Dec 13, 2023
e6f4917
feat: add message to warn users about metrics collection (#6)
peter-rr Dec 18, 2023
60d149d
Merge remote-tracking branch 'origin/master' into feat/adoptionMetrics
smoya Jan 12, 2024
7a22c5f
update @smoya/asyncapi-adoption-metrics to latest version
smoya Jan 12, 2024
d57655b
chore: unify logic for metrics collection when any command is execute…
peter-rr Jan 12, 2024
16a38a5
Add METRICS_COLLECTION markdown file
peter-rr Jan 18, 2024
7c08679
Update document
peter-rr Jan 19, 2024
67801cf
Add remaining info to the document
peter-rr Jan 22, 2024
463911d
Add relative link to disable tracking section
peter-rr Jan 22, 2024
d53c0ec
Update link to section in the same document
peter-rr Jan 24, 2024
afd053c
Move markdown document to 'docs' folder
peter-rr Jan 24, 2024
33759d2
feat: stop sending metrics when CLI command is executed on CI/CD pipe…
peter-rr Jan 30, 2024
188bc72
Update document with requested changes
peter-rr Jan 30, 2024
64120fd
Rename one of the metrics on the document
peter-rr Jan 30, 2024
4e22ebf
feat: rename one of the metrics (#12)
peter-rr Jan 31, 2024
85efb45
Fix issue coming from 'bundle' command (#11)
peter-rr Jan 31, 2024
be26e67
Convert 'optimizations' array into an object
peter-rr Jan 31, 2024
e0f6949
Apply JSON format to 'optimizations' array
peter-rr Feb 2, 2024
75e0a2d
Adapt metadata to New Relic required format for attributes
peter-rr Feb 5, 2024
8d227f3
Clean some code
peter-rr Feb 5, 2024
e6c25fa
Reduce cognitive complexity
peter-rr Feb 5, 2024
d096c54
Refactor to reach the cognitive complexity allowed
peter-rr Feb 6, 2024
5fe9bc4
Clean some repeated code
peter-rr Feb 6, 2024
22e98ae
Apply suggested changes
peter-rr Feb 7, 2024
ab57855
Change some metadata to camel case
peter-rr Feb 7, 2024
737f3ee
Solve cognitive complexity issue and fix failing test
peter-rr Feb 7, 2024
397efd5
Reduce the complexity of metrics collection
peter-rr Feb 8, 2024
2bd4077
Change warning message to informational message about metrics usage
peter-rr Feb 13, 2024
131c52a
Update 'How to disable tracking' section
peter-rr Feb 13, 2024
e3fc3c7
Add new command to disable analytics
peter-rr Feb 16, 2024
42aa925
Update md document and warning message
peter-rr Feb 19, 2024
68e2488
Merge branch 'feat/adoptionMetrics' into feat/adoptMetrics-metrics-docs
peter-rr Feb 19, 2024
18059c9
Merge pull request #13 from peter-rr/fix/adoptMetrics-optimize-issue
peter-rr Feb 19, 2024
edb7f38
Merge pull request #9 from peter-rr/feat/adoptMetrics-metrics-docs
peter-rr Feb 19, 2024
e1a315c
Solve some sonarcloud issues in base.ts
peter-rr Feb 19, 2024
4b26d58
Merge branch 'master' into feat/adoptionMetrics
peter-rr Feb 19, 2024
a9b7feb
Add '--enable' flag to analytics command
peter-rr Feb 19, 2024
aaf7731
Get rid of ASYNCAPI_METRICS env variable
peter-rr Feb 20, 2024
1b5a62b
Update MD document and info message about metrics
peter-rr Feb 20, 2024
e16f70c
Get rid of readFileSync, using fs promises instead
peter-rr Feb 20, 2024
a5d1e95
Update 'catch' with more specific errors
peter-rr Feb 20, 2024
783f77a
Refactor code for enabling/disabling analytics
peter-rr Feb 20, 2024
8a22fab
update @smoya/asyncapi-adoption-metrics to latest version
smoya Feb 20, 2024
9b0abd3
update @smoya/asyncapi-adoption-metrics and @asyncapi/parser to lates…
smoya Feb 20, 2024
671f4fd
Solve linter issues
peter-rr Feb 20, 2024
3436ccf
Merge branch 'master' into feat/adoptionMetrics
peter-rr Feb 22, 2024
135cdff
Update @smoya/asyncapi-adoption-metrics to latest version
peter-rr Feb 23, 2024
b5928e7
Add user_id metadata
peter-rr Feb 23, 2024
c9945ee
Solve linter issue
peter-rr Feb 23, 2024
95f755d
Solve sonarcloud issues
peter-rr Feb 27, 2024
a8d52a1
Merge branch 'master' into feat/adoptionMetrics
peter-rr Feb 27, 2024
9382563
Merge branch 'master' into feat/adoptionMetrics
peter-rr Feb 27, 2024
2f468d0
Add integration tests for analytics command
peter-rr Feb 28, 2024
e51dcc3
Merge branch 'master' into feat/adoptionMetrics
peter-rr Feb 28, 2024
0f3da30
Solve linter issues
peter-rr Feb 28, 2024
49a2be1
Add 'source' metadata from hash generated
peter-rr Mar 1, 2024
a21c8d6
Modify generateSHA1() function to be async
peter-rr Mar 1, 2024
2c9331a
Solve failing tests
peter-rr Mar 5, 2024
b9ee8da
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 5, 2024
5d7e726
Solve sonarcloud security issue
peter-rr Mar 5, 2024
4452b44
Solve another sonarcloud issue
peter-rr Mar 5, 2024
0d1059c
Add analytics metadata to 'new' command
peter-rr Mar 5, 2024
9527057
Fix issue related to generateSHA256()
peter-rr Mar 6, 2024
ed03700
Merge remote-tracking branch 'upstream/master' into fix/adoptMetrics-…
peter-rr Mar 7, 2024
e5c3081
Solve more conflicts in package-lock.json
peter-rr Mar 7, 2024
de5b49f
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 8, 2024
48ba1a9
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 8, 2024
9ad9d87
Solve new conflicts
peter-rr Mar 8, 2024
eba927a
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 11, 2024
a8e6621
Merge branch 'master' into feat/adoptionMetrics
smoya Mar 11, 2024
9f1be17
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 12, 2024
fcfe8ee
Disable 'source' metadata collection
peter-rr Mar 12, 2024
43f3b0b
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 13, 2024
dd4362f
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 14, 2024
5d0cd26
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 14, 2024
eacbf73
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 14, 2024
1ce7cda
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 14, 2024
70aad0d
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 15, 2024
9c371ce
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 18, 2024
ee9f626
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 19, 2024
9cd11e8
Update asyncapi-adoption-metrics to latest version
peter-rr Mar 19, 2024
eb0111b
Update multi-parser to latest version
peter-rr Mar 19, 2024
2305ab4
Merge branch 'master' into feat/adoptionMetrics
peter-rr Mar 25, 2024
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
2 changes: 2 additions & 0 deletions bin/run
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env node

process.env.NODE_ENV = 'development';

const oclif = require('@oclif/core');

oclif.run()
Expand Down
14 changes: 14 additions & 0 deletions bin/run_bin
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env node

// Only the binary installed through NPM is considered production environment. See "bin" in package.json.
process.env.NODE_ENV = 'production';

const oclif = require('@oclif/core');

oclif.run()
.then(require('@oclif/core/flush'))
.catch((err) => {
const oclifHandler = require('@oclif/core/handle');
return oclifHandler(err.message);
});

3 changes: 3 additions & 0 deletions bin/run_bin.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@echo off

node "%~dp0\run_bin" %*
40 changes: 33 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "1.2.35",
"author": "@asyncapi",
"bin": {
"asyncapi": "./bin/run"
"asyncapi": "./bin/run_bin"
},
"bugs": "https://github.com/asyncapi/cli/issues",
"dependencies": {
Expand All @@ -23,6 +23,7 @@
"@oclif/core": "^1.26.2",
"@oclif/errors": "^1.3.6",
"@oclif/plugin-not-found": "^2.3.22",
"@smoya/asyncapi-adoption-metrics": "^2.3.0",
"@smoya/multi-parser": "^4.0.0",
"@stoplight/spectral-cli": "6.9.0",
"ajv": "^8.12.0",
Expand Down
89 changes: 89 additions & 0 deletions src/base.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
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() {
// noop
}
}

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 {
return await super.catch(err);
Expand All @@ -11,4 +31,73 @@ export default abstract class extends Command {
}
}
}

async recordActionExecuted(action: string, metadata: MetricMetadata = {}, rawDocument?: string) {
if (rawDocument !== undefined) {
try {
const {document} = await this.parser.parse(rawDocument);
if (document !== undefined) {
metadata = MetadataFromDocument(document, metadata);
}
} catch (e: any) {
if (e instanceof Error) {
this.log(`Skipping submitting anonymous metrics due to the following error: ${e.name}: ${e.message}`);
}
}
}

const callable = async function(recorder: Recorder) {
await recorder.recordActionExecuted(action, metadata);
};

await this.recordActionMetric(callable);
}

async recordActionInvoked(action: string, metadata?: MetricMetadata) {
const callable = async function(recorder: Recorder) {
await recorder.recordActionInvoked(action, metadata);
};

await this.recordActionMetric(callable);
}

async recordActionMetric(recordFunc: (recorder: Recorder) => Promise<void>) {
try {
await recordFunc(this.recorder);
await this.recorder.flush();
} catch (e: any) {
if (e instanceof Error) {
this.log(`Skipping submitting anonymous metrics due to the following error: ${e.name}: ${e.message}`);
}
}
}

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 {
let sink: Sink = new DiscardSink();
if (process.env.ASYNCAPI_METRICS !== 'false') {
switch (process.env.NODE_ENV) {
case 'development':
// NODE_ENV set to `development` in bin/run
if (!process.env.TEST) {
// Do not pollute stdout when running tests
sink = new StdOutSink();
}
break;
case 'production':
// NODE_ENV set to `production` in bin/run_bin, which is specified in 'bin' package.json section
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;
}
}

return new Recorder(prefix, sink);
}
}

19 changes: 19 additions & 0 deletions src/commands/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import bundle from '@asyncapi/bundler';
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 All @@ -26,13 +28,17 @@ export default class Bundle extends Command {
base: Flags.string({ char: 'b', description: 'Path to the file which will act as a base. This is required when some properties are to needed to be overwritten.' }),
};

parser = new Parser();

async run() {
const { argv, flags } = await this.parse(Bundle);
const output = flags.output;
let baseFile;
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 @@ -50,6 +56,8 @@ export default class Bundle extends Command {
base: baseFile
}
);

await this.collectMetricsData(document);

if (!output) {
if (outputFormat === '.yaml' || outputFormat === '.yml') {
Expand All @@ -75,6 +83,17 @@ export default class Bundle extends Command {
}
}

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[]> {
const files = [];
for (const filepath of filepaths) {
Expand Down
15 changes: 7 additions & 8 deletions src/commands/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +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 type { ConvertVersion } from '@asyncapi/converter';

// @ts-ignore
Expand All @@ -30,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 Down
7 changes: 6 additions & 1 deletion src/commands/generate/fromTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { watchFlag } from '../../flags';
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 @@ -107,6 +107,8 @@ export default class Template extends Command {
{ name: 'template', description: '- Name of the generator template like for example @asyncapi/html-template or https://github.com/asyncapi/html-template', required: true }
];

parser = new Parser();

async run() {
const { args, flags } = await this.parse(Template); // NOSONAR

Expand All @@ -124,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 Down
Loading