mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[APM] Minor watcher improvements (#18602)
* [APM] Improve watcher tests * Update tests
This commit is contained in:
parent
bed97a27b0
commit
fdc24f5b3f
8 changed files with 276 additions and 35 deletions
|
@ -53,6 +53,7 @@
|
|||
"jest-cli": "^22.4.3",
|
||||
"jest-styled-components": "^5.0.1",
|
||||
"mocha": "^5.0.5",
|
||||
"mustache": "^2.3.0",
|
||||
"node-fetch": "^2.1.2",
|
||||
"pdf-image": "1.1.0",
|
||||
"pixelmatch": "4.0.2",
|
||||
|
|
|
@ -25,7 +25,7 @@ describe('ErrorGroupOverview -> List', () => {
|
|||
const storeState = {};
|
||||
const wrapper = mount(
|
||||
<MemoryRouter>
|
||||
<List items={[]} urlParams={props.urlParams} />
|
||||
<List items={[]} urlParams={props.urlParams} location={{}} />
|
||||
</MemoryRouter>,
|
||||
storeState
|
||||
);
|
||||
|
|
|
@ -149,8 +149,14 @@ export default class WatcherFlyout extends Component {
|
|||
|
||||
const timeRange =
|
||||
this.state.schedule === 'interval'
|
||||
? `now-${this.state.interval.value}${this.state.interval.unit}`
|
||||
: 'now-24h';
|
||||
? {
|
||||
value: this.state.interval.value,
|
||||
unit: this.state.interval.unit
|
||||
}
|
||||
: {
|
||||
value: 24,
|
||||
unit: 'h'
|
||||
};
|
||||
|
||||
return createErrorGroupWatch({
|
||||
emails,
|
||||
|
|
|
@ -1,31 +1,53 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`createErrorGroupWatch should call createWatch with correct args 1`] = `
|
||||
exports[`createErrorGroupWatch should format email correctly 1`] = `
|
||||
"Your service \\"opbeans-node\\" has error groups which exceeds 10 occurrences within \\"24h\\"
|
||||
|
||||
|
||||
<strong>this is a string</strong>
|
||||
N/A
|
||||
7761 occurrences
|
||||
|
||||
<strong>foo</strong>
|
||||
<anonymous> (server/coffee.js)
|
||||
7752 occurrences
|
||||
|
||||
<strong>socket hang up</strong>
|
||||
createHangUpError (_http_client.js)
|
||||
3887 occurrences
|
||||
|
||||
<strong>this will not get captured by express</strong>
|
||||
<anonymous> (server/coffee.js)
|
||||
3886 occurrences
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`createErrorGroupWatch should format entire template correctly 1`] = `
|
||||
Object {
|
||||
"actions": Object {
|
||||
"email": Object {
|
||||
"email": Object {
|
||||
"body": Object {
|
||||
"html": "Your service \\"{{ctx.metadata.serviceName}}\\" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within \\"{{ctx.metadata.timeRangeHumanReadable}}\\"<br/><br/>{{#ctx.payload.aggregations.error_groups.buckets}}<br/><strong>{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.message}}{{/sample.hits.hits.0._source.error.log.message}}</strong><br/>{{sample.hits.hits.0._source.error.culprit}}{{^sample.hits.hits.0._source.error.culprit}}N/A{{/sample.hits.hits.0._source.error.culprit}}<br/>{{doc_count}} occurrences<br/>{{/ctx.payload.aggregations.error_groups.buckets}}",
|
||||
"html": "Your service \\"opbeans-node\\" has error groups which exceeds 10 occurrences within \\"24h\\"<br/><br/><br/><strong>this is a string</strong><br/>N/A<br/>7761 occurrences<br/><br/><strong>foo</strong><br/><anonymous> (server/coffee.js)<br/>7752 occurrences<br/><br/><strong>socket hang up</strong><br/>createHangUpError (_http_client.js)<br/>3887 occurrences<br/><br/><strong>this will not get captured by express</strong><br/><anonymous> (server/coffee.js)<br/>3886 occurrences<br/>",
|
||||
},
|
||||
"subject": "\\"{{ctx.metadata.serviceName}}\\" has error groups which exceeds the threshold",
|
||||
"to": "{{ctx.metadata.emails}}",
|
||||
"subject": "\\"opbeans-node\\" has error groups which exceeds the threshold",
|
||||
"to": "my@email.dk",
|
||||
},
|
||||
},
|
||||
"log_error": Object {
|
||||
"logging": Object {
|
||||
"text": "Your service \\"{{ctx.metadata.serviceName}}\\" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within \\"{{ctx.metadata.timeRangeHumanReadable}}\\"<br/><br/>{{#ctx.payload.aggregations.error_groups.buckets}}<br/><strong>{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.message}}{{/sample.hits.hits.0._source.error.log.message}}</strong><br/>{{sample.hits.hits.0._source.error.culprit}}{{^sample.hits.hits.0._source.error.culprit}}N/A{{/sample.hits.hits.0._source.error.culprit}}<br/>{{doc_count}} occurrences<br/>{{/ctx.payload.aggregations.error_groups.buckets}}",
|
||||
"text": "Your service \\"opbeans-node\\" has error groups which exceeds 10 occurrences within \\"24h\\"<br/><br/><br/><strong>this is a string</strong><br/>N/A<br/>7761 occurrences<br/><br/><strong>foo</strong><br/><anonymous> (server/coffee.js)<br/>7752 occurrences<br/><br/><strong>socket hang up</strong><br/>createHangUpError (_http_client.js)<br/>3887 occurrences<br/><br/><strong>this will not get captured by express</strong><br/><anonymous> (server/coffee.js)<br/>3886 occurrences<br/>",
|
||||
},
|
||||
},
|
||||
"slack_webhook": Object {
|
||||
"webhook": Object {
|
||||
"body": "{\\"text\\":\\"Your service \\\\\\"{{ctx.metadata.serviceName}}\\\\\\" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within \\\\\\"{{ctx.metadata.timeRangeHumanReadable}}\\\\\\"\\\\n{{#ctx.payload.aggregations.error_groups.buckets}}\\\\n>*{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.message}}{{/sample.hits.hits.0._source.error.log.message}}*\\\\n>{{#sample.hits.hits.0._source.error.culprit}}\`{{sample.hits.hits.0._source.error.culprit}}\`{{/sample.hits.hits.0._source.error.culprit}}{{^sample.hits.hits.0._source.error.culprit}}N/A{{/sample.hits.hits.0._source.error.culprit}}\\\\n>{{doc_count}} occurrences\\\\n{{/ctx.payload.aggregations.error_groups.buckets}}\\"}",
|
||||
"body": "{\\"text\\":\\"Your service \\\\\\"opbeans-node\\\\\\" has error groups which exceeds 10 occurrences within \\\\\\"24h\\\\\\"\\\\n\\\\n>*this is a string*\\\\n>N/A\\\\n>7761 occurrences\\\\n\\\\n>*foo*\\\\n>\`<anonymous> (server/coffee.js)\`\\\\n>7752 occurrences\\\\n\\\\n>*socket hang up*\\\\n>\`createHangUpError (_http_client.js)\`\\\\n>3887 occurrences\\\\n\\\\n>*this will not get captured by express*\\\\n>\`<anonymous> (server/coffee.js)\`\\\\n>3886 occurrences\\\\n\\"}",
|
||||
"headers": Object {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
"host": "hooks.slack.com",
|
||||
"method": "POST",
|
||||
"path": "{{ctx.metadata.slackUrlPath}}",
|
||||
"path": "/services/slackid1/slackid2/slackid3",
|
||||
"port": 443,
|
||||
"scheme": "https",
|
||||
},
|
||||
|
@ -64,7 +86,7 @@ Object {
|
|||
},
|
||||
"terms": Object {
|
||||
"field": "error.grouping_key",
|
||||
"min_doc_count": "{{ctx.metadata.threshold}}",
|
||||
"min_doc_count": "10",
|
||||
"order": Object {
|
||||
"_count": "desc",
|
||||
},
|
||||
|
@ -77,7 +99,7 @@ Object {
|
|||
"filter": Array [
|
||||
Object {
|
||||
"term": Object {
|
||||
"context.service.name": "{{ctx.metadata.serviceName}}",
|
||||
"context.service.name": "opbeans-node",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
|
@ -88,7 +110,7 @@ Object {
|
|||
Object {
|
||||
"range": Object {
|
||||
"@timestamp": Object {
|
||||
"gte": "{{ctx.metadata.timeRange}}",
|
||||
"gte": "now-24h",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -110,8 +132,8 @@ Object {
|
|||
"serviceName": "opbeans-node",
|
||||
"slackUrlPath": "/services/slackid1/slackid2/slackid3",
|
||||
"threshold": 10,
|
||||
"timeRange": "now-24h",
|
||||
"timeRangeHumanReadable": "24h",
|
||||
"timeRangeUnit": "h",
|
||||
"timeRangeValue": 24,
|
||||
"trigger": "This value must be changed in trigger section",
|
||||
},
|
||||
"trigger": Object {
|
||||
|
@ -125,10 +147,22 @@ Object {
|
|||
`;
|
||||
|
||||
exports[`createErrorGroupWatch should format slack message correctly 1`] = `
|
||||
"Your service \\"{{ctx.metadata.serviceName}}\\" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within \\"{{ctx.metadata.timeRangeHumanReadable}}\\"
|
||||
{{#ctx.payload.aggregations.error_groups.buckets}}
|
||||
>*{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.message}}{{/sample.hits.hits.0._source.error.log.message}}*
|
||||
>{{#sample.hits.hits.0._source.error.culprit}}\`{{sample.hits.hits.0._source.error.culprit}}\`{{/sample.hits.hits.0._source.error.culprit}}{{^sample.hits.hits.0._source.error.culprit}}N/A{{/sample.hits.hits.0._source.error.culprit}}
|
||||
>{{doc_count}} occurrences
|
||||
{{/ctx.payload.aggregations.error_groups.buckets}}"
|
||||
"Your service \\"opbeans-node\\" has error groups which exceeds 10 occurrences within \\"24h\\"
|
||||
|
||||
>*this is a string*
|
||||
>N/A
|
||||
>7761 occurrences
|
||||
|
||||
>*foo*
|
||||
>\`<anonymous> (server/coffee.js)\`
|
||||
>7752 occurrences
|
||||
|
||||
>*socket hang up*
|
||||
>\`createHangUpError (_http_client.js)\`
|
||||
>3887 occurrences
|
||||
|
||||
>*this will not get captured by express*
|
||||
>\`<anonymous> (server/coffee.js)\`
|
||||
>3886 occurrences
|
||||
"
|
||||
`;
|
||||
|
|
|
@ -5,16 +5,27 @@
|
|||
*/
|
||||
|
||||
import { createErrorGroupWatch } from '../createErrorGroupWatch';
|
||||
import mustache from 'mustache';
|
||||
import chrome from 'ui/chrome';
|
||||
import * as rest from '../../../../../services/rest';
|
||||
import { isObject, isArray, isString } from 'lodash';
|
||||
import esResponse from './esResponse.json';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: jest.fn(() => 'mocked-uuid')
|
||||
}));
|
||||
|
||||
// disable html escaping since this is also disabled in watcher\s mustache implementation
|
||||
mustache.escape = value => value;
|
||||
|
||||
describe('createErrorGroupWatch', () => {
|
||||
let res;
|
||||
let createWatchResponse;
|
||||
let tmpl;
|
||||
beforeEach(async () => {
|
||||
chrome.getInjected = jest.fn().mockReturnValue('myIndexPattern');
|
||||
jest.spyOn(rest, 'createWatch').mockReturnValue();
|
||||
|
||||
res = await createErrorGroupWatch({
|
||||
createWatchResponse = await createErrorGroupWatch({
|
||||
emails: ['my@email.dk'],
|
||||
schedule: {
|
||||
daily: {
|
||||
|
@ -24,27 +35,70 @@ describe('createErrorGroupWatch', () => {
|
|||
serviceName: 'opbeans-node',
|
||||
slackUrl: 'https://hooks.slack.com/services/slackid1/slackid2/slackid3',
|
||||
threshold: 10,
|
||||
timeRange: 'now-24h'
|
||||
timeRange: { value: 24, unit: 'h' }
|
||||
});
|
||||
|
||||
const watchBody = rest.createWatch.mock.calls[0][1];
|
||||
const templateCtx = {
|
||||
payload: esResponse,
|
||||
metadata: watchBody.metadata
|
||||
};
|
||||
|
||||
tmpl = renderMustache(rest.createWatch.mock.calls[0][1], templateCtx);
|
||||
});
|
||||
|
||||
afterEach(() => jest.restoreAllMocks());
|
||||
|
||||
it('should call createWatch with correct args', () => {
|
||||
expect(rest.createWatch.mock.calls[0][0]).toContain('apm-');
|
||||
expect(rest.createWatch.mock.calls[0][1]).toMatchSnapshot();
|
||||
expect(rest.createWatch.mock.calls[0][0]).toBe('apm-mocked-uuid');
|
||||
});
|
||||
|
||||
it('should format slack message correctly', () => {
|
||||
expect(tmpl.actions.slack_webhook.webhook.path).toBe(
|
||||
'/services/slackid1/slackid2/slackid3'
|
||||
);
|
||||
expect(
|
||||
JSON.parse(
|
||||
rest.createWatch.mock.calls[0][1].actions.slack_webhook.webhook.body
|
||||
).text
|
||||
JSON.parse(tmpl.actions.slack_webhook.webhook.body).text
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should format email correctly', () => {
|
||||
expect(tmpl.actions.email.email.to).toBe('my@email.dk');
|
||||
expect(tmpl.actions.email.email.subject).toBe(
|
||||
'"opbeans-node" has error groups which exceeds the threshold'
|
||||
);
|
||||
expect(
|
||||
tmpl.actions.email.email.body.html.replace(/<br\/>/g, '\n')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should format entire template correctly', () => {
|
||||
expect(tmpl).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return watch id', async () => {
|
||||
const id = rest.createWatch.mock.calls[0][0];
|
||||
expect(res).toEqual(id);
|
||||
expect(createWatchResponse).toEqual(id);
|
||||
});
|
||||
});
|
||||
|
||||
// Recusively iterate a nested structure and render strings as mustache templates
|
||||
function renderMustache(input, ctx) {
|
||||
if (isString(input)) {
|
||||
return mustache.render(input, { ctx });
|
||||
}
|
||||
|
||||
if (isArray(input)) {
|
||||
return input.map(itemValue => renderMustache(itemValue, ctx));
|
||||
}
|
||||
|
||||
if (isObject(input)) {
|
||||
return Object.keys(input).reduce((acc, key) => {
|
||||
const value = input[key];
|
||||
|
||||
return { ...acc, [key]: renderMustache(value, ctx) };
|
||||
}, {});
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
{
|
||||
"took": 454,
|
||||
"timed_out": false,
|
||||
"_shards": {
|
||||
"total": 10,
|
||||
"successful": 10,
|
||||
"skipped": 0,
|
||||
"failed": 0
|
||||
},
|
||||
"hits": {
|
||||
"total": 23287,
|
||||
"max_score": 0,
|
||||
"hits": []
|
||||
},
|
||||
"aggregations": {
|
||||
"error_groups": {
|
||||
"doc_count_error_upper_bound": 0,
|
||||
"sum_other_doc_count": 0,
|
||||
"buckets": [
|
||||
{
|
||||
"key": "63925d00b445cdf4b532dd09d185f5c6",
|
||||
"doc_count": 7761,
|
||||
"sample": {
|
||||
"hits": {
|
||||
"total": 7761,
|
||||
"max_score": null,
|
||||
"hits": [
|
||||
{
|
||||
"_index": "apm-7.0.0-alpha1-error-2018.04.25",
|
||||
"_type": "doc",
|
||||
"_id": "qH7C_WIBcmGuKeCHJvvT",
|
||||
"_score": null,
|
||||
"_source": {
|
||||
"@timestamp": "2018-04-25T17:03:02.296Z",
|
||||
"error": {
|
||||
"log": {
|
||||
"message": "this is a string"
|
||||
},
|
||||
"grouping_key": "63925d00b445cdf4b532dd09d185f5c6"
|
||||
}
|
||||
},
|
||||
"sort": [1524675782296]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "89bb1a1f644c7f4bbe8d1781b5cb5fd5",
|
||||
"doc_count": 7752,
|
||||
"sample": {
|
||||
"hits": {
|
||||
"total": 7752,
|
||||
"max_score": null,
|
||||
"hits": [
|
||||
{
|
||||
"_index": "apm-7.0.0-alpha1-error-2018.04.25",
|
||||
"_type": "doc",
|
||||
"_id": "_3_D_WIBcmGuKeCHFwOW",
|
||||
"_score": null,
|
||||
"_source": {
|
||||
"@timestamp": "2018-04-25T17:04:03.504Z",
|
||||
"error": {
|
||||
"exception": {
|
||||
"handled": true,
|
||||
"message": "foo"
|
||||
},
|
||||
"culprit": "<anonymous> (server/coffee.js)",
|
||||
"grouping_key": "89bb1a1f644c7f4bbe8d1781b5cb5fd5"
|
||||
}
|
||||
},
|
||||
"sort": [1524675843504]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "7a17ea60604e3531bd8de58645b8631f",
|
||||
"doc_count": 3887,
|
||||
"sample": {
|
||||
"hits": {
|
||||
"total": 3887,
|
||||
"max_score": null,
|
||||
"hits": [
|
||||
{
|
||||
"_index": "apm-7.0.0-alpha1-error-2018.04.25",
|
||||
"_type": "doc",
|
||||
"_id": "dn_D_WIBcmGuKeCHQgXJ",
|
||||
"_score": null,
|
||||
"_source": {
|
||||
"@timestamp": "2018-04-25T17:04:14.575Z",
|
||||
"error": {
|
||||
"exception": {
|
||||
"handled": false,
|
||||
"message": "socket hang up"
|
||||
},
|
||||
"culprit": "createHangUpError (_http_client.js)",
|
||||
"grouping_key": "7a17ea60604e3531bd8de58645b8631f"
|
||||
}
|
||||
},
|
||||
"sort": [1524675854575]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "b9e1027f29c221763f864f6fa2ad9f5e",
|
||||
"doc_count": 3886,
|
||||
"sample": {
|
||||
"hits": {
|
||||
"total": 3886,
|
||||
"max_score": null,
|
||||
"hits": [
|
||||
{
|
||||
"_index": "apm-7.0.0-alpha1-error-2018.04.25",
|
||||
"_type": "doc",
|
||||
"_id": "dX_D_WIBcmGuKeCHQgXJ",
|
||||
"_score": null,
|
||||
"_source": {
|
||||
"@timestamp": "2018-04-25T17:04:14.533Z",
|
||||
"error": {
|
||||
"exception": {
|
||||
"handled": false,
|
||||
"message": "this will not get captured by express"
|
||||
},
|
||||
"culprit": "<anonymous> (server/coffee.js)",
|
||||
"grouping_key": "b9e1027f29c221763f864f6fa2ad9f5e"
|
||||
}
|
||||
},
|
||||
"sort": [1524675854533]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ export async function createErrorGroupWatch({
|
|||
const apmIndexPattern = chrome.getInjected('apmIndexPattern');
|
||||
|
||||
const slackUrlPath = getSlackPathUrl(slackUrl);
|
||||
const emailTemplate = `Your service "{{ctx.metadata.serviceName}}" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within "{{ctx.metadata.timeRangeHumanReadable}}"
|
||||
const emailTemplate = `Your service "{{ctx.metadata.serviceName}}" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within "{{ctx.metadata.timeRangeValue}}{{ctx.metadata.timeRangeUnit}}"
|
||||
|
||||
{{#ctx.payload.aggregations.error_groups.buckets}}
|
||||
<strong>{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.message}}{{/sample.hits.hits.0._source.error.log.message}}</strong>
|
||||
|
@ -46,7 +46,7 @@ export async function createErrorGroupWatch({
|
|||
{{doc_count}} occurrences
|
||||
{{/ctx.payload.aggregations.error_groups.buckets}}`.replace(/\n/g, '<br/>');
|
||||
|
||||
const slackTemplate = `Your service "{{ctx.metadata.serviceName}}" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within "{{ctx.metadata.timeRangeHumanReadable}}"
|
||||
const slackTemplate = `Your service "{{ctx.metadata.serviceName}}" has error groups which exceeds {{ctx.metadata.threshold}} occurrences within "{{ctx.metadata.timeRangeValue}}{{ctx.metadata.timeRangeUnit}}"
|
||||
{{#ctx.payload.aggregations.error_groups.buckets}}
|
||||
>*{{sample.hits.hits.0._source.error.log.message}}{{^sample.hits.hits.0._source.error.log.message}}{{sample.hits.hits.0._source.error.exception.message}}{{/sample.hits.hits.0._source.error.log.message}}*
|
||||
>{{#sample.hits.hits.0._source.error.culprit}}\`{{sample.hits.hits.0._source.error.culprit}}\`{{/sample.hits.hits.0._source.error.culprit}}{{^sample.hits.hits.0._source.error.culprit}}N/A{{/sample.hits.hits.0._source.error.culprit}}
|
||||
|
@ -59,8 +59,8 @@ export async function createErrorGroupWatch({
|
|||
trigger: 'This value must be changed in trigger section',
|
||||
serviceName,
|
||||
threshold,
|
||||
timeRange,
|
||||
timeRangeHumanReadable: timeRange.replace('now-', ''),
|
||||
timeRangeValue: timeRange.value,
|
||||
timeRangeUnit: timeRange.unit,
|
||||
slackUrlPath
|
||||
},
|
||||
trigger: {
|
||||
|
@ -80,7 +80,8 @@ export async function createErrorGroupWatch({
|
|||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: '{{ctx.metadata.timeRange}}'
|
||||
gte:
|
||||
'now-{{ctx.metadata.timeRangeValue}}{{ctx.metadata.timeRangeUnit}}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5175,6 +5175,10 @@ multipipe@^0.1.2:
|
|||
dependencies:
|
||||
duplexer2 "0.0.2"
|
||||
|
||||
mustache@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.0.tgz#4028f7778b17708a489930a6e52ac3bca0da41d0"
|
||||
|
||||
mute-stream@0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue