From 3a86d05c49addc8294e9dcf16600226d6b970385 Mon Sep 17 00:00:00 2001 From: Boris Chernysh Date: Sat, 8 Aug 2020 11:14:45 +0300 Subject: [PATCH] feat: Added the ability to pass labels as an object to labels() and remove() Typings: updated for labels and remove object args returned passed labels as string arguments in README returned a lost line from changelog --- CHANGELOG.md | 2 + README.md | 2 + index.d.ts | 52 ++++++++++++++++++++++++++ lib/counter.js | 3 +- lib/gauge.js | 2 + lib/histogram.js | 2 + lib/summary.js | 2 + lib/util.js | 4 ++ test/__snapshots__/counterTest.js.snap | 2 - test/counterTest.js | 28 ++++++++++---- test/gaugeTest.js | 14 +++++++ test/histogramTest.js | 14 +++++++ test/summaryTest.js | 17 +++++++++ 13 files changed, 134 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40c649cb..a8feed15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ project adheres to [Semantic Versioning](http://semver.org/). ### Added +- feat: added the ability to pass labels as an object to `labels()` and `remove()` + ## [13.0.0] - 2020-12-16 ### Breaking diff --git a/README.md b/README.md index b8da070e..9596e584 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,8 @@ const gauge = new client.Gauge({ // 1st version: Set value to 100 with "method" set to "GET" and "statusCode" to "200" gauge.set({ method: 'GET', statusCode: '200' }, 100); // 2nd version: Same effect as above +gauge.labels({ method: 'GET', statusCode: '200' }).set(100); +// 3nd version: And again the same effect as above gauge.labels('GET', '200').set(100); ``` diff --git a/index.d.ts b/index.d.ts index f5f3b934..cc60a609 100644 --- a/index.d.ts +++ b/index.d.ts @@ -184,6 +184,13 @@ export class Counter { */ labels(...values: string[]): Counter.Internal; + /** + * Return the child for given labels + * @param labels Object with label keys and values + * @return Configured counter with given labels + */ + labels(labels: LabelValues): Counter.Internal; + /** * Reset counter values */ @@ -194,6 +201,12 @@ export class Counter { * @param values Label values */ remove(...values: string[]): void; + + /** + * Remove metrics for the given label values + * @param labels Object with label keys and values + */ + remove(labels: LabelValues): void; } export namespace Counter { @@ -279,6 +292,13 @@ export class Gauge { */ labels(...values: string[]): Gauge.Internal; + /** + * Return the child for given labels + * @param labels Object with label keys and values + * @return Configured counter with given labels + */ + labels(labels: LabelValues): Gauge.Internal; + /** * Reset gauge values */ @@ -289,6 +309,12 @@ export class Gauge { * @param values Label values */ remove(...values: string[]): void; + + /** + * Remove metrics for the given label values + * @param labels Object with label keys and values + */ + remove(labels: LabelValues): void; } export namespace Gauge { @@ -370,11 +396,24 @@ export class Histogram { */ labels(...values: string[]): Histogram.Internal; + /** + * Return the child for given labels + * @param labels Object with label keys and values + * @return Configured counter with given labels + */ + labels(labels: LabelValues): Histogram.Internal; + /** * Remove metrics for the given label values * @param values Label values */ remove(...values: string[]): void; + + /** + * Remove metrics for the given label values + * @param labels Object with label keys and values + */ + remove(labels: LabelValues): void; } export namespace Histogram { @@ -450,11 +489,24 @@ export class Summary { */ labels(...values: string[]): Summary.Internal; + /** + * Return the child for given labels + * @param labels Object with label keys and values + * @return Configured counter with given labels + */ + labels(labels: LabelValues): Summary.Internal; + /** * Remove metrics for the given label values * @param values Label values */ remove(...values: string[]): void; + + /** + * Remove metrics for the given label values + * @param labels Object with label keys and values + */ + remove(labels: LabelValues): void; } export namespace Summary { diff --git a/lib/counter.js b/lib/counter.js index 8b3d9e5b..5ab8c9e9 100644 --- a/lib/counter.js +++ b/lib/counter.js @@ -49,8 +49,8 @@ class Counter extends Metric { labels() { const labels = getLabels(this.labelNames, arguments) || {}; - const hash = hashObject(labels); validateLabel(this.labelNames, labels); + const hash = hashObject(labels); return { inc: inc.call(this, labels, hash), }; @@ -58,6 +58,7 @@ class Counter extends Metric { remove() { const labels = getLabels(this.labelNames, arguments) || {}; + validateLabel(this.labelNames, labels); return removeLabels.call(this, this.hashMap, labels); } } diff --git a/lib/gauge.js b/lib/gauge.js index d9b80368..73f17a73 100644 --- a/lib/gauge.js +++ b/lib/gauge.js @@ -102,6 +102,7 @@ class Gauge extends Metric { labels() { const labels = getLabels(this.labelNames, arguments); + validateLabel(this.labelNames, labels); return { inc: inc.call(this, labels), dec: dec.call(this, labels), @@ -113,6 +114,7 @@ class Gauge extends Metric { remove() { const labels = getLabels(this.labelNames, arguments); + validateLabel(this.labelNames, labels); removeLabels.call(this, this.hashMap, labels); } } diff --git a/lib/histogram.js b/lib/histogram.js index a2ea6d3e..89fc2372 100644 --- a/lib/histogram.js +++ b/lib/histogram.js @@ -90,6 +90,7 @@ class Histogram extends Metric { labels() { const labels = getLabels(this.labelNames, arguments); + validateLabel(this.labelNames, labels); return { observe: observe.call(this, labels), startTimer: startTimer.call(this, labels), @@ -98,6 +99,7 @@ class Histogram extends Metric { remove() { const labels = getLabels(this.labelNames, arguments); + validateLabel(this.labelNames, labels); removeLabels.call(this, this.hashMap, labels); } } diff --git a/lib/summary.js b/lib/summary.js index 8f344b63..e5afd1b8 100644 --- a/lib/summary.js +++ b/lib/summary.js @@ -96,6 +96,7 @@ class Summary extends Metric { labels() { const labels = getLabels(this.labelNames, arguments); + validateLabel(this.labelNames, labels); return { observe: observe.call(this, labels), startTimer: startTimer.call(this, labels), @@ -104,6 +105,7 @@ class Summary extends Metric { remove() { const labels = getLabels(this.labelNames, arguments); + validateLabel(this.labelNames, labels); removeLabels.call(this, this.hashMap, labels); } } diff --git a/lib/util.js b/lib/util.js index ca23a19b..e5774b9e 100644 --- a/lib/util.js +++ b/lib/util.js @@ -30,6 +30,10 @@ exports.setValue = function setValue(hashMap, value, labels) { // TODO: For node 6, use rest params exports.getLabels = function (labelNames, args) { + if (typeof args[0] === 'object') { + return args[0]; + } + if (labelNames.length !== args.length) { throw new Error('Invalid number of arguments'); } diff --git a/test/__snapshots__/counterTest.js.snap b/test/__snapshots__/counterTest.js.snap index f5f2d4d0..e68024c3 100644 --- a/test/__snapshots__/counterTest.js.snap +++ b/test/__snapshots__/counterTest.js.snap @@ -4,8 +4,6 @@ exports[`counter remove should throw error if label lengths does not match 1`] = exports[`counter with params as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`counter with params as object labels should throw error if label lengths does not match 2`] = `"Invalid number of arguments"`; - exports[`counter with params as object should not be possible to decrease a counter 1`] = `"It is not possible to decrease a counter"`; exports[`counter with params as object should throw an error when the value is not a number 1`] = `"Value is not a valid number: 3ms"`; diff --git a/test/counterTest.js b/test/counterTest.js index 9e4596ce..ff16e512 100644 --- a/test/counterTest.js +++ b/test/counterTest.js @@ -65,6 +65,16 @@ describe('counter', () => { expect(values).toHaveLength(2); }); + it('should handle labels provided as an object', async () => { + instance.labels({ method: 'POST', endpoint: '/test' }).inc(); + const values = (await instance.get()).values; + expect(values).toHaveLength(1); + expect(values[0].labels).toEqual({ + method: 'POST', + endpoint: '/test', + }); + }); + it('should handle labels which are provided as arguments to inc()', async () => { instance.inc({ method: 'GET', endpoint: '/test' }); instance.inc({ method: 'POST', endpoint: '/test' }); @@ -80,13 +90,6 @@ describe('counter', () => { expect(fn).toThrowErrorMatchingSnapshot(); }); - it('should throw error if label lengths does not match', () => { - const fn = function () { - instance.labels('GET').inc(); - }; - expect(fn).toThrowErrorMatchingSnapshot(); - }); - it('should increment label value with provided value', async () => { instance.labels('GET', '/test').inc(100); const values = (await instance.get()).values; @@ -121,6 +124,17 @@ describe('counter', () => { expect(values[0].timestamp).toEqual(undefined); }); + it('should remove by labels object', async () => { + instance.remove({ method: 'POST', endpoint: '/test' }); + + const values = (await instance.get()).values; + expect(values).toHaveLength(1); + expect(values[0].labels).toEqual({ + method: 'GET', + endpoint: '/test', + }); + }); + it('should remove all labels', async () => { instance.remove('GET', '/test'); instance.remove('POST', '/test'); diff --git a/test/gaugeTest.js b/test/gaugeTest.js index 18273f7e..d9afc7dd 100644 --- a/test/gaugeTest.js +++ b/test/gaugeTest.js @@ -140,6 +140,13 @@ describe('gauge', () => { end({ code: '400' }); expect(startLabels).toEqual({ code: '200' }); }); + it('should handle labels provided as an object', async () => { + instance.labels({ code: '200' }).inc(); + + const values = (await instance.get()).values; + expect(values).toHaveLength(1); + expect(values[0].labels).toEqual({ code: '200' }); + }); }); describe('with remove', () => { @@ -159,6 +166,13 @@ describe('gauge', () => { expect(values[0].labels.code).toEqual('400'); expect(values[0].value).toEqual(0); }); + it('should remove by labels object', async () => { + instance.remove({ code: '200' }); + const values = (await instance.get()).values; + expect(values).toHaveLength(1); + expect(values[0].labels).toEqual({ code: '400' }); + expect(values[0].value).toEqual(0); + }); it('should be able to remove all labels', async () => { instance.remove('200'); instance.remove('400'); diff --git a/test/histogramTest.js b/test/histogramTest.js index edab4e16..2876aee0 100644 --- a/test/histogramTest.js +++ b/test/histogramTest.js @@ -269,6 +269,14 @@ describe('histogram', () => { end({ method: 'POST' }); expect(startLabels).toEqual({ method: 'GET' }); }); + + it('should handle labels provided as an object', async () => { + instance.labels({ method: 'GET' }).startTimer()(); + const values = (await instance.get()).values; + values.forEach(value => { + expect(value.labels.method).toBe('GET'); + }); + }); }); describe('remove', () => { @@ -355,6 +363,12 @@ describe('histogram', () => { expect((await instance.get()).values).toHaveLength(0); jest.useRealTimers(); }); + + it('should remove by labels object', async () => { + instance.observe({ method: 'GET' }, 1); + instance.remove({ method: 'GET' }); + expect((await instance.get()).values).toHaveLength(0); + }); }); }); diff --git a/test/summaryTest.js b/test/summaryTest.js index e644ae7f..bd119dfd 100644 --- a/test/summaryTest.js +++ b/test/summaryTest.js @@ -274,6 +274,14 @@ describe('summary', () => { end({ endpoint: '/test' }); expect(startLabels).toEqual({ method: 'GET' }); }); + + it('should handle labels provided as an object', async () => { + instance.labels({ method: 'GET' }).startTimer()(); + const values = (await instance.get()).values; + values.forEach(value => { + expect(value.labels.method).toBe('GET'); + }); + }); }); describe('remove', () => { @@ -408,6 +416,15 @@ describe('summary', () => { jest.useRealTimers(); }); + + it('should remove by labels object', async () => { + instance.observe({ endpoint: '/test' }, 1); + instance.remove({ endpoint: '/test' }); + const values = (await instance.get()).values; + values.forEach(value => { + expect(value.labels).not.toEqual({ endpoint: '/test' }); + }); + }); }); }); });