mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Response Ops][Actions] Logging errors from the mustache lambda format functions (#176014)
Resolves https://github.com/elastic/kibana/issues/173972 ## Summary Changing to log and return error string instead of throwing error inside the mustache format lambda functions. When an error occurs, the lambda will return the error message. This allows the rest of the action message to be rendered, while giving the user some indication an error occurred and logging the error in a way that is discoverable in the logs. ## To verify Create a rule and add a message like: ``` {{alert.id}} - {{#FormatDate}} {{{context.nope}}} ; America/New_York ; dddd MMM Do YYYY HH:mm:ss.SSS {{/FormatDate}} ``` When the rule runs and triggers and alert, you should see the alert ID in the notification message along with a `date is empty` message and an error log indicating that the date formatting was unsuccessful due to empty date. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
450f9f62e2
commit
11593f0b75
33 changed files with 404 additions and 194 deletions
|
@ -6,10 +6,16 @@
|
|||
*/
|
||||
|
||||
import dedent from 'dedent';
|
||||
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { renderMustacheString } from './mustache_renderer';
|
||||
|
||||
const logger = loggingSystemMock.create().get();
|
||||
|
||||
describe('mustache lambdas', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('FormatDate', () => {
|
||||
it('date with defaults is successful', () => {
|
||||
const timeStamp = '2022-11-29T15:52:44Z';
|
||||
|
@ -17,7 +23,9 @@ describe('mustache lambdas', () => {
|
|||
{{#FormatDate}} {{timeStamp}} {{/FormatDate}}
|
||||
`.trim();
|
||||
|
||||
expect(renderMustacheString(template, { timeStamp }, 'none')).toEqual('2022-11-29 03:52pm');
|
||||
expect(renderMustacheString(logger, template, { timeStamp }, 'none')).toEqual(
|
||||
'2022-11-29 03:52pm'
|
||||
);
|
||||
});
|
||||
|
||||
it('date with a time zone is successful', () => {
|
||||
|
@ -26,7 +34,9 @@ describe('mustache lambdas', () => {
|
|||
{{#FormatDate}} {{timeStamp}} ; America/New_York {{/FormatDate}}
|
||||
`.trim();
|
||||
|
||||
expect(renderMustacheString(template, { timeStamp }, 'none')).toEqual('2022-11-29 10:52am');
|
||||
expect(renderMustacheString(logger, template, { timeStamp }, 'none')).toEqual(
|
||||
'2022-11-29 10:52am'
|
||||
);
|
||||
});
|
||||
|
||||
it('date with a format is successful', () => {
|
||||
|
@ -35,7 +45,7 @@ describe('mustache lambdas', () => {
|
|||
{{#FormatDate}} {{timeStamp}} ;; dddd MMM Do YYYY HH:mm:ss.SSS {{/FormatDate}}
|
||||
`.trim();
|
||||
|
||||
expect(renderMustacheString(template, { timeStamp }, 'none')).toEqual(
|
||||
expect(renderMustacheString(logger, template, { timeStamp }, 'none')).toEqual(
|
||||
'Tuesday Nov 29th 2022 15:52:44.000'
|
||||
);
|
||||
});
|
||||
|
@ -46,41 +56,48 @@ describe('mustache lambdas', () => {
|
|||
{{#FormatDate}} {{timeStamp}};America/New_York;dddd MMM Do YYYY HH:mm:ss.SSS {{/FormatDate}}
|
||||
`.trim();
|
||||
|
||||
expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual(
|
||||
expect(renderMustacheString(logger, template, { timeStamp }, 'none').trim()).toEqual(
|
||||
'Tuesday Nov 29th 2022 10:52:44.000'
|
||||
);
|
||||
});
|
||||
|
||||
it('empty date produces error', () => {
|
||||
it('empty date logs and returns error string', () => {
|
||||
const timeStamp = '';
|
||||
const template = dedent`
|
||||
{{#FormatDate}} {{/FormatDate}}
|
||||
`.trim();
|
||||
|
||||
expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual(
|
||||
'error rendering mustache template "{{#FormatDate}} {{/FormatDate}}": date is empty'
|
||||
expect(renderMustacheString(logger, template, { timeStamp }, 'none').trim()).toEqual(
|
||||
'date is empty'
|
||||
);
|
||||
expect(logger.warn).toHaveBeenCalledWith(`mustache render error: date is empty`);
|
||||
});
|
||||
|
||||
it('invalid date produces error', () => {
|
||||
it('invalid date logs and returns error string', () => {
|
||||
const timeStamp = 'this is not a d4t3';
|
||||
const template = dedent`
|
||||
{{#FormatDate}}{{timeStamp}}{{/FormatDate}}
|
||||
`.trim();
|
||||
|
||||
expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual(
|
||||
'error rendering mustache template "{{#FormatDate}}{{timeStamp}}{{/FormatDate}}": invalid date "this is not a d4t3"'
|
||||
expect(renderMustacheString(logger, template, { timeStamp }, 'none').trim()).toEqual(
|
||||
'invalid date "this is not a d4t3"'
|
||||
);
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`mustache render error: invalid date "this is not a d4t3"`
|
||||
);
|
||||
});
|
||||
|
||||
it('invalid timezone produces error', () => {
|
||||
it('invalid timezone logs and returns error string', () => {
|
||||
const timeStamp = '2023-04-10T23:52:39';
|
||||
const template = dedent`
|
||||
{{#FormatDate}}{{timeStamp}};NotATime Zone!{{/FormatDate}}
|
||||
`.trim();
|
||||
|
||||
expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual(
|
||||
'error rendering mustache template "{{#FormatDate}}{{timeStamp}};NotATime Zone!{{/FormatDate}}": unknown timeZone value "NotATime Zone!"'
|
||||
expect(renderMustacheString(logger, template, { timeStamp }, 'none').trim()).toEqual(
|
||||
'unknown timeZone value "NotATime Zone!"'
|
||||
);
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`mustache render error: unknown timeZone value "NotATime Zone!"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -92,7 +109,7 @@ describe('mustache lambdas', () => {
|
|||
|
||||
// not clear how to force an error, it pretty much does something with
|
||||
// ANY string
|
||||
expect(renderMustacheString(template, { timeStamp }, 'none').trim()).toEqual(
|
||||
expect(renderMustacheString(logger, template, { timeStamp }, 'none').trim()).toEqual(
|
||||
'gamrbamg2' // a => am/pm (so am here); e => day of week
|
||||
);
|
||||
});
|
||||
|
@ -114,11 +131,11 @@ describe('mustache lambdas', () => {
|
|||
{{/context}}
|
||||
`.trim();
|
||||
|
||||
const result = renderMustacheString(template, vars, 'none');
|
||||
const result = renderMustacheString(logger, template, vars, 'none');
|
||||
expect(result).toEqual(`1\n2\n3\n`);
|
||||
});
|
||||
|
||||
it('invalid expression produces error', () => {
|
||||
it('invalid expression logs and returns error string', () => {
|
||||
const vars = {
|
||||
context: {
|
||||
a: { b: 1 },
|
||||
|
@ -129,9 +146,12 @@ describe('mustache lambdas', () => {
|
|||
{{#EvalMath}} ) 1 ++++ 0 ( {{/EvalMath}}
|
||||
`.trim();
|
||||
|
||||
const result = renderMustacheString(template, vars, 'none');
|
||||
const result = renderMustacheString(logger, template, vars, 'none');
|
||||
expect(result).toEqual(
|
||||
`error rendering mustache template "{{#EvalMath}} ) 1 ++++ 0 ( {{/EvalMath}}": error evaluating tinymath expression ") 1 ++++ 0 (": Failed to parse expression. Expected "(", function, literal, or whitespace but ")" found.`
|
||||
'error evaluating tinymath expression ") 1 ++++ 0 (": Failed to parse expression. Expected "(", function, literal, or whitespace but ")" found.'
|
||||
);
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`mustache render error: error evaluating tinymath expression ") 1 ++++ 0 (": Failed to parse expression. Expected "(", function, literal, or whitespace but ")" found.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -147,7 +167,7 @@ describe('mustache lambdas', () => {
|
|||
const hjson = `
|
||||
{
|
||||
# specify rate in requests/second (because comments are helpful!)
|
||||
rate: 1000
|
||||
rate: 1000
|
||||
|
||||
a: {{context.a}}
|
||||
a_b: {{context.a.b}}
|
||||
|
@ -166,7 +186,7 @@ describe('mustache lambdas', () => {
|
|||
{{#ParseHjson}} ${hjson} {{/ParseHjson}}
|
||||
`.trim();
|
||||
|
||||
const result = renderMustacheString(template, vars, 'none');
|
||||
const result = renderMustacheString(logger, template, vars, 'none');
|
||||
expect(JSON.parse(result)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"a": Object {
|
||||
|
@ -189,13 +209,18 @@ describe('mustache lambdas', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
it('renders an error message on parse errors', () => {
|
||||
it('logs an error message and returns error string on parse errors', () => {
|
||||
const template = dedent`
|
||||
{{#ParseHjson}} [1,2,3,,] {{/ParseHjson}}
|
||||
`.trim();
|
||||
|
||||
const result = renderMustacheString(template, {}, 'none');
|
||||
expect(result).toMatch(/^error rendering mustache template .*/);
|
||||
const result = renderMustacheString(logger, template, {}, 'none');
|
||||
expect(result).toEqual(
|
||||
`error parsing Hjson \"[1,2,3,,]\": Found a punctuator character ',' when expecting a quoteless string (check your syntax) at line 1,7 >>>1,2,3,,] ...`
|
||||
);
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`mustache render error: error parsing Hjson \"[1,2,3,,]\": Found a punctuator character ',' when expecting a quoteless string (check your syntax) at line 1,7 >>>1,2,3,,] ...`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -206,16 +231,19 @@ describe('mustache lambdas', () => {
|
|||
{{#FormatNumber}} {{num}}; en-US; style: currency, currency: EUR {{/FormatNumber}}
|
||||
`.trim();
|
||||
|
||||
expect(renderMustacheString(template, { num }, 'none')).toEqual('€42.00');
|
||||
expect(renderMustacheString(logger, template, { num }, 'none')).toEqual('€42.00');
|
||||
});
|
||||
|
||||
it('renders an error message on errors', () => {
|
||||
it('logs an error message and returns empty string on errors', () => {
|
||||
const num = 'nope;;';
|
||||
const template = dedent`
|
||||
{{#FormatNumber}} {{num}} {{/FormatNumber}}
|
||||
`.trim();
|
||||
|
||||
expect(renderMustacheString(template, { num }, 'none')).toEqual(`invalid number: 'nope'`);
|
||||
expect(renderMustacheString(logger, template, { num }, 'none')).toEqual(
|
||||
`invalid number: 'nope'`
|
||||
);
|
||||
expect(logger.warn).toHaveBeenCalledWith(`mustache render error: invalid number: 'nope'`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import * as tinymath from '@kbn/tinymath';
|
||||
import { parse as hjsonParse } from 'hjson';
|
||||
import moment from 'moment-timezone';
|
||||
import { Logger } from '@kbn/core/server';
|
||||
|
||||
import { formatNumber } from './number_formatter';
|
||||
|
||||
|
@ -16,96 +17,102 @@ type Variables = Record<string, unknown>;
|
|||
const DefaultDateTimeZone = 'UTC';
|
||||
const DefaultDateFormat = 'YYYY-MM-DD hh:mma';
|
||||
|
||||
export function getMustacheLambdas(): Variables {
|
||||
return getLambdas();
|
||||
export function getMustacheLambdas(logger: Logger): Variables {
|
||||
return getLambdas(logger);
|
||||
}
|
||||
|
||||
const TimeZoneSet = new Set(moment.tz.names());
|
||||
|
||||
type RenderFn = (text: string) => string;
|
||||
|
||||
function getLambdas() {
|
||||
function getLambdas(logger: Logger) {
|
||||
return {
|
||||
EvalMath: () =>
|
||||
// mustache invokes lamdas with `this` set to the current "view" (variables)
|
||||
function (this: Variables, text: string, render: RenderFn) {
|
||||
return evalMath(this, render(text.trim()));
|
||||
return evalMath(this, render(text.trim()), logger);
|
||||
},
|
||||
ParseHjson: () =>
|
||||
function (text: string, render: RenderFn) {
|
||||
return parseHjson(render(text.trim()));
|
||||
return parseHjson(render(text.trim()), logger);
|
||||
},
|
||||
FormatDate: () =>
|
||||
function (text: string, render: RenderFn) {
|
||||
const dateString = render(text.trim()).trim();
|
||||
return formatDate(dateString);
|
||||
return formatDate(dateString, logger);
|
||||
},
|
||||
FormatNumber: () =>
|
||||
function (text: string, render: RenderFn) {
|
||||
const numberString = render(text.trim()).trim();
|
||||
return formatNumber(numberString);
|
||||
return formatNumber(logger, numberString);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function evalMath(vars: Variables, o: unknown): string {
|
||||
function evalMath(vars: Variables, o: unknown, logger: Logger): string {
|
||||
const expr = `${o}`;
|
||||
try {
|
||||
const result = tinymath.evaluate(expr, vars);
|
||||
return `${result}`;
|
||||
} catch (err) {
|
||||
throw new Error(`error evaluating tinymath expression "${expr}": ${err.message}`);
|
||||
return logAndReturnErr(
|
||||
logger,
|
||||
`error evaluating tinymath expression "${expr}": ${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function parseHjson(o: unknown): string {
|
||||
function parseHjson(o: unknown, logger: Logger): string {
|
||||
const hjsonObject = `${o}`;
|
||||
let object: unknown;
|
||||
|
||||
try {
|
||||
object = hjsonParse(hjsonObject);
|
||||
} catch (err) {
|
||||
throw new Error(`error parsing Hjson "${hjsonObject}": ${err.message}`);
|
||||
return logAndReturnErr(logger, `error parsing Hjson "${hjsonObject}": ${err.message}`);
|
||||
}
|
||||
|
||||
return JSON.stringify(object);
|
||||
}
|
||||
|
||||
function formatDate(dateString: unknown): string {
|
||||
function formatDate(dateString: unknown, logger: Logger): string {
|
||||
const { date, timeZone, format } = splitDateString(`${dateString}`);
|
||||
|
||||
if (date === '') {
|
||||
throw new Error(`date is empty`);
|
||||
return logAndReturnErr(logger, `date is empty`);
|
||||
}
|
||||
|
||||
if (isNaN(new Date(date).valueOf())) {
|
||||
throw new Error(`invalid date "${date}"`);
|
||||
return logAndReturnErr(logger, `invalid date "${date}"`);
|
||||
}
|
||||
|
||||
let mDate: moment.Moment;
|
||||
try {
|
||||
mDate = moment(date);
|
||||
if (!mDate.isValid()) {
|
||||
throw new Error(`date is invalid`);
|
||||
return logAndReturnErr(logger, `invalid date "${date}"`);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(`error evaluating moment date "${date}": ${err.message}`);
|
||||
return logAndReturnErr(logger, `error evaluating moment date "${date}": ${err.message}`);
|
||||
}
|
||||
|
||||
if (!TimeZoneSet.has(timeZone)) {
|
||||
throw new Error(`unknown timeZone value "${timeZone}"`);
|
||||
return logAndReturnErr(logger, `unknown timeZone value "${timeZone}"`);
|
||||
}
|
||||
|
||||
try {
|
||||
mDate.tz(timeZone);
|
||||
} catch (err) {
|
||||
throw new Error(`error evaluating moment timeZone "${timeZone}": ${err.message}`);
|
||||
return logAndReturnErr(
|
||||
logger,
|
||||
`error evaluating moment timeZone "${timeZone}": ${err.message}`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return mDate.format(format);
|
||||
} catch (err) {
|
||||
throw new Error(`error evaluating moment format "${format}": ${err.message}`);
|
||||
return logAndReturnErr(logger, `error evaluating moment format "${format}": ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,3 +125,8 @@ function splitDateString(dateString: string) {
|
|||
format: format || DefaultDateFormat,
|
||||
};
|
||||
}
|
||||
|
||||
function logAndReturnErr(logger: Logger, errMessage: string): string {
|
||||
logger.warn(`mustache render error: ${errMessage}`);
|
||||
return errMessage;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import {
|
||||
renderMustacheString,
|
||||
renderMustacheStringNoEscape,
|
||||
|
@ -12,6 +13,8 @@ import {
|
|||
Escape,
|
||||
} from './mustache_renderer';
|
||||
|
||||
const logger = loggingSystemMock.create().get();
|
||||
|
||||
const variables = {
|
||||
a: 1,
|
||||
b: '2',
|
||||
|
@ -38,97 +41,121 @@ const variables = {
|
|||
};
|
||||
|
||||
describe('mustache_renderer', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('renderMustacheString()', () => {
|
||||
for (const escapeVal of ['none', 'slack', 'markdown', 'json']) {
|
||||
const escape = escapeVal as Escape;
|
||||
|
||||
it(`handles basic templating that does not need escaping for ${escape}`, () => {
|
||||
expect(renderMustacheString('', variables, escape)).toBe('');
|
||||
expect(renderMustacheString('{{a}}', variables, escape)).toBe('1');
|
||||
expect(renderMustacheString('{{b}}', variables, escape)).toBe('2');
|
||||
expect(renderMustacheString('{{c}}', variables, escape)).toBe('false');
|
||||
expect(renderMustacheString('{{d}}', variables, escape)).toBe('');
|
||||
expect(renderMustacheString('{{e}}', variables, escape)).toBe('');
|
||||
expect(renderMustacheString(logger, '', variables, escape)).toBe('');
|
||||
expect(renderMustacheString(logger, '{{a}}', variables, escape)).toBe('1');
|
||||
expect(renderMustacheString(logger, '{{b}}', variables, escape)).toBe('2');
|
||||
expect(renderMustacheString(logger, '{{c}}', variables, escape)).toBe('false');
|
||||
expect(renderMustacheString(logger, '{{d}}', variables, escape)).toBe('');
|
||||
expect(renderMustacheString(logger, '{{e}}', variables, escape)).toBe('');
|
||||
if (escape === 'json') {
|
||||
expect(renderMustacheString('{{f}}', variables, escape)).toBe('{\\"g\\":3,\\"h\\":null}');
|
||||
expect(renderMustacheString(logger, '{{f}}', variables, escape)).toBe(
|
||||
'{\\"g\\":3,\\"h\\":null}'
|
||||
);
|
||||
} else if (escape === 'markdown') {
|
||||
expect(renderMustacheString('{{f}}', variables, escape)).toBe('\\{"g":3,"h":null\\}');
|
||||
expect(renderMustacheString(logger, '{{f}}', variables, escape)).toBe(
|
||||
'\\{"g":3,"h":null\\}'
|
||||
);
|
||||
} else {
|
||||
expect(renderMustacheString('{{f}}', variables, escape)).toBe('{"g":3,"h":null}');
|
||||
expect(renderMustacheString(logger, '{{f}}', variables, escape)).toBe('{"g":3,"h":null}');
|
||||
}
|
||||
expect(renderMustacheString('{{f.g}}', variables, escape)).toBe('3');
|
||||
expect(renderMustacheString('{{f.h}}', variables, escape)).toBe('');
|
||||
expect(renderMustacheString('{{i}}', variables, escape)).toBe('42,43,44');
|
||||
expect(renderMustacheString(logger, '{{f.g}}', variables, escape)).toBe('3');
|
||||
expect(renderMustacheString(logger, '{{f.h}}', variables, escape)).toBe('');
|
||||
expect(renderMustacheString(logger, '{{i}}', variables, escape)).toBe('42,43,44');
|
||||
|
||||
if (escape === 'markdown') {
|
||||
expect(renderMustacheString('{{i.asJSON}}', variables, escape)).toBe('\\[42,43,44\\]');
|
||||
expect(renderMustacheString(logger, '{{i.asJSON}}', variables, escape)).toBe(
|
||||
'\\[42,43,44\\]'
|
||||
);
|
||||
} else {
|
||||
expect(renderMustacheString('{{i.asJSON}}', variables, escape)).toBe('[42,43,44]');
|
||||
expect(renderMustacheString(logger, '{{i.asJSON}}', variables, escape)).toBe(
|
||||
'[42,43,44]'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it('handles escape:none with commonly escaped strings', () => {
|
||||
expect(renderMustacheString('{{lt}}', variables, 'none')).toBe(variables.lt);
|
||||
expect(renderMustacheString('{{gt}}', variables, 'none')).toBe(variables.gt);
|
||||
expect(renderMustacheString('{{amp}}', variables, 'none')).toBe(variables.amp);
|
||||
expect(renderMustacheString('{{nl}}', variables, 'none')).toBe(variables.nl);
|
||||
expect(renderMustacheString('{{dq}}', variables, 'none')).toBe(variables.dq);
|
||||
expect(renderMustacheString('{{bt}}', variables, 'none')).toBe(variables.bt);
|
||||
expect(renderMustacheString('{{bs}}', variables, 'none')).toBe(variables.bs);
|
||||
expect(renderMustacheString('{{st}}', variables, 'none')).toBe(variables.st);
|
||||
expect(renderMustacheString('{{ul}}', variables, 'none')).toBe(variables.ul);
|
||||
expect(renderMustacheString(logger, '{{lt}}', variables, 'none')).toBe(variables.lt);
|
||||
expect(renderMustacheString(logger, '{{gt}}', variables, 'none')).toBe(variables.gt);
|
||||
expect(renderMustacheString(logger, '{{amp}}', variables, 'none')).toBe(variables.amp);
|
||||
expect(renderMustacheString(logger, '{{nl}}', variables, 'none')).toBe(variables.nl);
|
||||
expect(renderMustacheString(logger, '{{dq}}', variables, 'none')).toBe(variables.dq);
|
||||
expect(renderMustacheString(logger, '{{bt}}', variables, 'none')).toBe(variables.bt);
|
||||
expect(renderMustacheString(logger, '{{bs}}', variables, 'none')).toBe(variables.bs);
|
||||
expect(renderMustacheString(logger, '{{st}}', variables, 'none')).toBe(variables.st);
|
||||
expect(renderMustacheString(logger, '{{ul}}', variables, 'none')).toBe(variables.ul);
|
||||
});
|
||||
|
||||
it('handles escape:markdown with commonly escaped strings', () => {
|
||||
expect(renderMustacheString('{{lt}}', variables, 'markdown')).toBe(variables.lt);
|
||||
expect(renderMustacheString('{{gt}}', variables, 'markdown')).toBe(variables.gt);
|
||||
expect(renderMustacheString('{{amp}}', variables, 'markdown')).toBe(variables.amp);
|
||||
expect(renderMustacheString('{{nl}}', variables, 'markdown')).toBe(variables.nl);
|
||||
expect(renderMustacheString('{{dq}}', variables, 'markdown')).toBe(variables.dq);
|
||||
expect(renderMustacheString('{{bt}}', variables, 'markdown')).toBe('\\' + variables.bt);
|
||||
expect(renderMustacheString('{{bs}}', variables, 'markdown')).toBe('\\' + variables.bs);
|
||||
expect(renderMustacheString('{{st}}', variables, 'markdown')).toBe('\\' + variables.st);
|
||||
expect(renderMustacheString('{{ul}}', variables, 'markdown')).toBe('\\' + variables.ul);
|
||||
expect(renderMustacheString('{{vl}}', variables, 'markdown')).toBe('\\' + variables.vl);
|
||||
expect(renderMustacheString(logger, '{{lt}}', variables, 'markdown')).toBe(variables.lt);
|
||||
expect(renderMustacheString(logger, '{{gt}}', variables, 'markdown')).toBe(variables.gt);
|
||||
expect(renderMustacheString(logger, '{{amp}}', variables, 'markdown')).toBe(variables.amp);
|
||||
expect(renderMustacheString(logger, '{{nl}}', variables, 'markdown')).toBe(variables.nl);
|
||||
expect(renderMustacheString(logger, '{{dq}}', variables, 'markdown')).toBe(variables.dq);
|
||||
expect(renderMustacheString(logger, '{{bt}}', variables, 'markdown')).toBe(
|
||||
'\\' + variables.bt
|
||||
);
|
||||
expect(renderMustacheString(logger, '{{bs}}', variables, 'markdown')).toBe(
|
||||
'\\' + variables.bs
|
||||
);
|
||||
expect(renderMustacheString(logger, '{{st}}', variables, 'markdown')).toBe(
|
||||
'\\' + variables.st
|
||||
);
|
||||
expect(renderMustacheString(logger, '{{ul}}', variables, 'markdown')).toBe(
|
||||
'\\' + variables.ul
|
||||
);
|
||||
expect(renderMustacheString(logger, '{{vl}}', variables, 'markdown')).toBe(
|
||||
'\\' + variables.vl
|
||||
);
|
||||
});
|
||||
|
||||
it('handles triple escapes', () => {
|
||||
expect(renderMustacheString('{{{bt}}}', variables, 'markdown')).toBe(variables.bt);
|
||||
expect(renderMustacheString('{{{bs}}}', variables, 'markdown')).toBe(variables.bs);
|
||||
expect(renderMustacheString('{{{st}}}', variables, 'markdown')).toBe(variables.st);
|
||||
expect(renderMustacheString('{{{ul}}}', variables, 'markdown')).toBe(variables.ul);
|
||||
expect(renderMustacheString(logger, '{{{bt}}}', variables, 'markdown')).toBe(variables.bt);
|
||||
expect(renderMustacheString(logger, '{{{bs}}}', variables, 'markdown')).toBe(variables.bs);
|
||||
expect(renderMustacheString(logger, '{{{st}}}', variables, 'markdown')).toBe(variables.st);
|
||||
expect(renderMustacheString(logger, '{{{ul}}}', variables, 'markdown')).toBe(variables.ul);
|
||||
});
|
||||
|
||||
it('handles escape:slack with commonly escaped strings', () => {
|
||||
expect(renderMustacheString('{{lt}}', variables, 'slack')).toBe('<');
|
||||
expect(renderMustacheString('{{gt}}', variables, 'slack')).toBe('>');
|
||||
expect(renderMustacheString('{{amp}}', variables, 'slack')).toBe('&');
|
||||
expect(renderMustacheString('{{nl}}', variables, 'slack')).toBe(variables.nl);
|
||||
expect(renderMustacheString('{{dq}}', variables, 'slack')).toBe(variables.dq);
|
||||
expect(renderMustacheString('{{bt}}', variables, 'slack')).toBe(`'`);
|
||||
expect(renderMustacheString('{{bs}}', variables, 'slack')).toBe(variables.bs);
|
||||
expect(renderMustacheString('{{st}}', variables, 'slack')).toBe('`*`');
|
||||
expect(renderMustacheString('{{ul}}', variables, 'slack')).toBe('`_`');
|
||||
expect(renderMustacheString(logger, '{{lt}}', variables, 'slack')).toBe('<');
|
||||
expect(renderMustacheString(logger, '{{gt}}', variables, 'slack')).toBe('>');
|
||||
expect(renderMustacheString(logger, '{{amp}}', variables, 'slack')).toBe('&');
|
||||
expect(renderMustacheString(logger, '{{nl}}', variables, 'slack')).toBe(variables.nl);
|
||||
expect(renderMustacheString(logger, '{{dq}}', variables, 'slack')).toBe(variables.dq);
|
||||
expect(renderMustacheString(logger, '{{bt}}', variables, 'slack')).toBe(`'`);
|
||||
expect(renderMustacheString(logger, '{{bs}}', variables, 'slack')).toBe(variables.bs);
|
||||
expect(renderMustacheString(logger, '{{st}}', variables, 'slack')).toBe('`*`');
|
||||
expect(renderMustacheString(logger, '{{ul}}', variables, 'slack')).toBe('`_`');
|
||||
// html escapes not needed when using backtic escaping
|
||||
expect(renderMustacheString('{{st_lt}}', variables, 'slack')).toBe('`*<`');
|
||||
expect(renderMustacheString('{{link}}', variables, 'slack')).toBe('https://te_st.com/');
|
||||
expect(renderMustacheString(logger, '{{st_lt}}', variables, 'slack')).toBe('`*<`');
|
||||
expect(renderMustacheString(logger, '{{link}}', variables, 'slack')).toBe(
|
||||
'https://te_st.com/'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles escape:json with commonly escaped strings', () => {
|
||||
expect(renderMustacheString('{{lt}}', variables, 'json')).toBe(variables.lt);
|
||||
expect(renderMustacheString('{{gt}}', variables, 'json')).toBe(variables.gt);
|
||||
expect(renderMustacheString('{{amp}}', variables, 'json')).toBe(variables.amp);
|
||||
expect(renderMustacheString('{{nl}}', variables, 'json')).toBe('\\n');
|
||||
expect(renderMustacheString('{{dq}}', variables, 'json')).toBe('\\"');
|
||||
expect(renderMustacheString('{{bt}}', variables, 'json')).toBe(variables.bt);
|
||||
expect(renderMustacheString('{{bs}}', variables, 'json')).toBe('\\\\');
|
||||
expect(renderMustacheString('{{st}}', variables, 'json')).toBe(variables.st);
|
||||
expect(renderMustacheString('{{ul}}', variables, 'json')).toBe(variables.ul);
|
||||
expect(renderMustacheString(logger, '{{lt}}', variables, 'json')).toBe(variables.lt);
|
||||
expect(renderMustacheString(logger, '{{gt}}', variables, 'json')).toBe(variables.gt);
|
||||
expect(renderMustacheString(logger, '{{amp}}', variables, 'json')).toBe(variables.amp);
|
||||
expect(renderMustacheString(logger, '{{nl}}', variables, 'json')).toBe('\\n');
|
||||
expect(renderMustacheString(logger, '{{dq}}', variables, 'json')).toBe('\\"');
|
||||
expect(renderMustacheString(logger, '{{bt}}', variables, 'json')).toBe(variables.bt);
|
||||
expect(renderMustacheString(logger, '{{bs}}', variables, 'json')).toBe('\\\\');
|
||||
expect(renderMustacheString(logger, '{{st}}', variables, 'json')).toBe(variables.st);
|
||||
expect(renderMustacheString(logger, '{{ul}}', variables, 'json')).toBe(variables.ul);
|
||||
});
|
||||
|
||||
it('handles errors', () => {
|
||||
expect(renderMustacheString('{{a}', variables, 'none')).toMatchInlineSnapshot(
|
||||
expect(renderMustacheString(logger, '{{a}', variables, 'none')).toMatchInlineSnapshot(
|
||||
`"error rendering mustache template \\"{{a}\\": Unclosed tag at 4"`
|
||||
);
|
||||
});
|
||||
|
@ -285,7 +312,7 @@ describe('mustache_renderer', () => {
|
|||
|
||||
describe('renderMustacheObject()', () => {
|
||||
it('handles deep objects', () => {
|
||||
expect(renderMustacheObject(object, variables)).toMatchInlineSnapshot(`
|
||||
expect(renderMustacheObject(logger, object, variables)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"list": Array [
|
||||
"1",
|
||||
|
@ -311,12 +338,12 @@ describe('mustache_renderer', () => {
|
|||
});
|
||||
|
||||
it('handles primitive objects', () => {
|
||||
expect(renderMustacheObject(undefined, variables)).toMatchInlineSnapshot(`undefined`);
|
||||
expect(renderMustacheObject(null, variables)).toMatchInlineSnapshot(`null`);
|
||||
expect(renderMustacheObject(0, variables)).toMatchInlineSnapshot(`0`);
|
||||
expect(renderMustacheObject(true, variables)).toMatchInlineSnapshot(`true`);
|
||||
expect(renderMustacheObject('{{a}}', variables)).toMatchInlineSnapshot(`"1"`);
|
||||
expect(renderMustacheObject(['{{a}}'], variables)).toMatchInlineSnapshot(`
|
||||
expect(renderMustacheObject(logger, undefined, variables)).toMatchInlineSnapshot(`undefined`);
|
||||
expect(renderMustacheObject(logger, null, variables)).toMatchInlineSnapshot(`null`);
|
||||
expect(renderMustacheObject(logger, 0, variables)).toMatchInlineSnapshot(`0`);
|
||||
expect(renderMustacheObject(logger, true, variables)).toMatchInlineSnapshot(`true`);
|
||||
expect(renderMustacheObject(logger, '{{a}}', variables)).toMatchInlineSnapshot(`"1"`);
|
||||
expect(renderMustacheObject(logger, ['{{a}}'], variables)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"1",
|
||||
]
|
||||
|
@ -324,7 +351,7 @@ describe('mustache_renderer', () => {
|
|||
});
|
||||
|
||||
it('handles errors', () => {
|
||||
expect(renderMustacheObject({ a: '{{a}' }, variables)).toMatchInlineSnapshot(`
|
||||
expect(renderMustacheObject(logger, { a: '{{a}' }, variables)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"a": "error rendering mustache template \\"{{a}\\": Unclosed tag at 4",
|
||||
}
|
||||
|
@ -338,7 +365,7 @@ describe('mustache_renderer', () => {
|
|||
b: { c: 2, d: [3, 4] },
|
||||
e: [5, { f: 6, g: 7 }],
|
||||
};
|
||||
expect(renderMustacheObject({ x: '{{a}} - {{b}} -- {{e}} ' }, deepVariables))
|
||||
expect(renderMustacheObject(logger, { x: '{{a}} - {{b}} -- {{e}} ' }, deepVariables))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"x": "1 - {\\"c\\":2,\\"d\\":[3,4]} -- 5,{\\"f\\":6,\\"g\\":7} ",
|
||||
|
@ -346,10 +373,12 @@ describe('mustache_renderer', () => {
|
|||
`);
|
||||
|
||||
const expected = '1 - {"c":2,"d":[3,4]} -- 5,{"f":6,"g":7}';
|
||||
expect(renderMustacheString('{{a}} - {{b}} -- {{e}}', deepVariables, 'none')).toEqual(expected);
|
||||
expect(renderMustacheString(logger, '{{a}} - {{b}} -- {{e}}', deepVariables, 'none')).toEqual(
|
||||
expected
|
||||
);
|
||||
|
||||
expect(renderMustacheString('{{e}}', deepVariables, 'none')).toEqual('5,{"f":6,"g":7}');
|
||||
expect(renderMustacheString('{{e.asJSON}}', deepVariables, 'none')).toEqual(
|
||||
expect(renderMustacheString(logger, '{{e}}', deepVariables, 'none')).toEqual('5,{"f":6,"g":7}');
|
||||
expect(renderMustacheString(logger, '{{e.asJSON}}', deepVariables, 'none')).toEqual(
|
||||
'[5,{"f":6,"g":7}]'
|
||||
);
|
||||
});
|
||||
|
@ -395,6 +424,7 @@ describe('mustache_renderer', () => {
|
|||
|
||||
expect(
|
||||
renderMustacheObject(
|
||||
logger,
|
||||
{
|
||||
x: '{{context.0._source.kibana.alert.rule.category}} - {{context.0._score.test}} - {{context.0._source.kibana.alert.time_range.gte}}',
|
||||
},
|
||||
|
@ -408,6 +438,7 @@ describe('mustache_renderer', () => {
|
|||
|
||||
expect(
|
||||
renderMustacheString(
|
||||
logger,
|
||||
'{{context.0._source.kibana.alert.rule.category}} - {{context.0._score.test}} - {{context.0._source.kibana.alert.time_range.gte}}',
|
||||
dotVariables,
|
||||
'none'
|
||||
|
@ -416,12 +447,13 @@ describe('mustache_renderer', () => {
|
|||
});
|
||||
|
||||
it('should replace single value with the object', () => {
|
||||
expect(renderMustacheObject({ x: '{{a}}' }, { a: 1, 'a.b': 2 })).toMatchInlineSnapshot(`
|
||||
expect(renderMustacheObject(logger, { x: '{{a}}' }, { a: 1, 'a.b': 2 }))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"x": "{\\"b\\":2}",
|
||||
}
|
||||
`);
|
||||
expect(renderMustacheString('{{a}}', { a: 1, 'a.b': 2 }, 'none')).toEqual('{"b":2}');
|
||||
expect(renderMustacheString(logger, '{{a}}', { a: 1, 'a.b': 2 }, 'none')).toEqual('{"b":2}');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Mustache from 'mustache';
|
||||
import { isString, isPlainObject, cloneDeepWith, merge } from 'lodash';
|
||||
import { Logger } from '@kbn/core/server';
|
||||
import { getMustacheLambdas } from './mustache_lambdas';
|
||||
|
||||
export type Escape = 'markdown' | 'slack' | 'json' | 'none';
|
||||
|
@ -25,9 +26,14 @@ export function renderMustacheStringNoEscape(string: string, variables: Variable
|
|||
}
|
||||
|
||||
// return a rendered mustache template given the specified variables and escape
|
||||
export function renderMustacheString(string: string, variables: Variables, escape: Escape): string {
|
||||
export function renderMustacheString(
|
||||
logger: Logger,
|
||||
string: string,
|
||||
variables: Variables,
|
||||
escape: Escape
|
||||
): string {
|
||||
const augmentedVariables = augmentObjectVariables(variables);
|
||||
const lambdas = getMustacheLambdas();
|
||||
const lambdas = getMustacheLambdas(logger);
|
||||
|
||||
const previousMustacheEscape = Mustache.escape;
|
||||
Mustache.escape = getEscape(escape);
|
||||
|
@ -43,13 +49,17 @@ export function renderMustacheString(string: string, variables: Variables, escap
|
|||
}
|
||||
|
||||
// return a cloned object with all strings rendered as mustache templates
|
||||
export function renderMustacheObject<Params>(params: Params, variables: Variables): Params {
|
||||
export function renderMustacheObject<Params>(
|
||||
logger: Logger,
|
||||
params: Params,
|
||||
variables: Variables
|
||||
): Params {
|
||||
const augmentedVariables = augmentObjectVariables(variables);
|
||||
const result = cloneDeepWith(params, (value: unknown) => {
|
||||
if (!isString(value)) return;
|
||||
|
||||
// since we're rendering a JS object, no escaping needed
|
||||
return renderMustacheString(value, augmentedVariables, 'none');
|
||||
return renderMustacheString(logger, value, augmentedVariables, 'none');
|
||||
});
|
||||
|
||||
// The return type signature for `cloneDeep()` ends up taking the return
|
||||
|
|
|
@ -5,52 +5,76 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { formatNumber } from './number_formatter';
|
||||
|
||||
const logger = loggingSystemMock.create().get();
|
||||
|
||||
describe('formatNumber()', () => {
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('using defaults is successful', () => {
|
||||
expect(formatNumber('1;;')).toMatchInlineSnapshot(`"1"`);
|
||||
expect(formatNumber(logger, '1;;')).toMatchInlineSnapshot(`"1"`);
|
||||
});
|
||||
|
||||
it('error cases handled', () => {
|
||||
expect(formatNumber('1')).toMatchInlineSnapshot(`"invalid format, missing semicolons: '1'"`);
|
||||
expect(formatNumber('nope;;')).toMatchInlineSnapshot(`"invalid number: 'nope'"`);
|
||||
expect(formatNumber('1;; nah')).toMatchInlineSnapshot(
|
||||
`"invalid options: missing colon in option: 'nah'"`
|
||||
expect(formatNumber(logger, '1')).toEqual(`invalid format, missing semicolons: '1'`);
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`mustache render error: invalid format, missing semicolons: '1'`
|
||||
);
|
||||
expect(formatNumber('1;; minimumIntegerDigits: N.O.')).toMatchInlineSnapshot(
|
||||
`"error formatting number: minimumIntegerDigits value is out of range."`
|
||||
|
||||
expect(formatNumber(logger, 'nope;;')).toEqual(`invalid number: 'nope'`);
|
||||
expect(logger.warn).toHaveBeenCalledWith(`mustache render error: invalid number: 'nope'`);
|
||||
|
||||
expect(formatNumber(logger, '1;; nah')).toEqual(
|
||||
`invalid options: missing colon in option: 'nah'`
|
||||
);
|
||||
expect(formatNumber('1;; compactDisplay: uhuh')).toMatchInlineSnapshot(
|
||||
`"error formatting number: Value uhuh out of range for Intl.NumberFormat options property compactDisplay"`
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`mustache render error: invalid options: missing colon in option: 'nah'`
|
||||
);
|
||||
|
||||
expect(formatNumber(logger, '1;; minimumIntegerDigits: N.O.')).toEqual(
|
||||
'error formatting number: minimumIntegerDigits value is out of range.'
|
||||
);
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`mustache render error: error formatting number: minimumIntegerDigits value is out of range.`
|
||||
);
|
||||
|
||||
expect(formatNumber(logger, '1;; compactDisplay: uhuh')).toEqual(
|
||||
'error formatting number: Value uhuh out of range for Intl.NumberFormat options property compactDisplay'
|
||||
);
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
`mustache render error: error formatting number: Value uhuh out of range for Intl.NumberFormat options property compactDisplay`
|
||||
);
|
||||
});
|
||||
|
||||
it('using locales is successful', () => {
|
||||
expect(formatNumber('1000; de-DE;')).toMatchInlineSnapshot(`"1.000"`);
|
||||
expect(formatNumber(logger, '1000; de-DE;')).toMatchInlineSnapshot(`"1.000"`);
|
||||
});
|
||||
|
||||
it('option compactDisplay is successful', () => {
|
||||
expect(
|
||||
formatNumber(' 1000;; notation: compact, compactDisplay: short, ')
|
||||
formatNumber(logger, ' 1000;; notation: compact, compactDisplay: short, ')
|
||||
).toMatchInlineSnapshot(`"1K"`);
|
||||
});
|
||||
|
||||
it('option currency is successful', () => {
|
||||
expect(formatNumber('1000;; currency: EUR, style: currency')).toMatchInlineSnapshot(
|
||||
expect(formatNumber(logger, '1000;; currency: EUR, style: currency')).toMatchInlineSnapshot(
|
||||
`"€1,000.00"`
|
||||
);
|
||||
});
|
||||
|
||||
it('option currencyDisplay is successful', () => {
|
||||
expect(
|
||||
formatNumber('1000;; currency: EUR, style: currency, currencyDisplay: name')
|
||||
formatNumber(logger, '1000;; currency: EUR, style: currency, currencyDisplay: name')
|
||||
).toMatchInlineSnapshot(`"1,000.00 euros"`);
|
||||
});
|
||||
|
||||
it('option currencySign is successful', () => {
|
||||
expect(
|
||||
formatNumber('-1;; currency: EUR, style: currency, currencySign: accounting')
|
||||
formatNumber(logger, '-1;; currency: EUR, style: currency, currencySign: accounting')
|
||||
).toMatchInlineSnapshot(`"(€1.00)"`);
|
||||
});
|
||||
|
||||
|
@ -60,34 +84,36 @@ describe('formatNumber()', () => {
|
|||
it.skip('option localeMatcher is successful', () => {});
|
||||
|
||||
it('option notation is successful', () => {
|
||||
expect(formatNumber('1000;; notation: engineering')).toMatchInlineSnapshot(`"1E3"`);
|
||||
expect(formatNumber(logger, '1000;; notation: engineering')).toMatchInlineSnapshot(`"1E3"`);
|
||||
});
|
||||
|
||||
it('option numberingSystem is successful', () => {
|
||||
expect(formatNumber('1;; numberingSystem: fullwide')).toMatchInlineSnapshot(`"1"`);
|
||||
expect(formatNumber(logger, '1;; numberingSystem: fullwide')).toMatchInlineSnapshot(`"1"`);
|
||||
});
|
||||
|
||||
it('option signDisplay is successful', () => {
|
||||
expect(formatNumber('1;; signDisplay: always')).toMatchInlineSnapshot(`"+1"`);
|
||||
expect(formatNumber(logger, '1;; signDisplay: always')).toMatchInlineSnapshot(`"+1"`);
|
||||
});
|
||||
|
||||
it('option style is successful', () => {
|
||||
expect(formatNumber('1;; style: percent')).toMatchInlineSnapshot(`"100%"`);
|
||||
expect(formatNumber(logger, '1;; style: percent')).toMatchInlineSnapshot(`"100%"`);
|
||||
});
|
||||
|
||||
it('option unit is successful', () => {
|
||||
expect(formatNumber('1;; style: unit, unit: acre-per-liter')).toMatchInlineSnapshot(`"1 ac/L"`);
|
||||
expect(formatNumber(logger, '1;; style: unit, unit: acre-per-liter')).toMatchInlineSnapshot(
|
||||
`"1 ac/L"`
|
||||
);
|
||||
});
|
||||
|
||||
it('option unitDisplay is successful', () => {
|
||||
expect(
|
||||
formatNumber('1;; style: unit, unit: petabyte, unitDisplay: narrow')
|
||||
formatNumber(logger, '1;; style: unit, unit: petabyte, unitDisplay: narrow')
|
||||
).toMatchInlineSnapshot(`"1PB"`);
|
||||
});
|
||||
|
||||
it('option useGrouping is successful', () => {
|
||||
expect(formatNumber('1000;; useGrouping: true ')).toMatchInlineSnapshot(`"1,000"`);
|
||||
expect(formatNumber('1000;; useGrouping: false')).toMatchInlineSnapshot(`"1000"`);
|
||||
expect(formatNumber(logger, '1000;; useGrouping: true ')).toMatchInlineSnapshot(`"1,000"`);
|
||||
expect(formatNumber(logger, '1000;; useGrouping: false')).toMatchInlineSnapshot(`"1000"`);
|
||||
});
|
||||
|
||||
// not yet supported in node.js
|
||||
|
@ -103,22 +129,28 @@ describe('formatNumber()', () => {
|
|||
it.skip('option trailingZeroDisplay is successful', () => {});
|
||||
|
||||
it('option minimumIntegerDigits is successful', () => {
|
||||
expect(formatNumber('1;; minimumIntegerDigits: 7')).toMatchInlineSnapshot(`"0,000,001"`);
|
||||
expect(formatNumber(logger, '1;; minimumIntegerDigits: 7')).toMatchInlineSnapshot(
|
||||
`"0,000,001"`
|
||||
);
|
||||
});
|
||||
|
||||
it('option minimumFractionDigits is successful', () => {
|
||||
expect(formatNumber('1;; minimumFractionDigits: 3')).toMatchInlineSnapshot(`"1.000"`);
|
||||
expect(formatNumber(logger, '1;; minimumFractionDigits: 3')).toMatchInlineSnapshot(`"1.000"`);
|
||||
});
|
||||
|
||||
it('option maximumFractionDigits is successful', () => {
|
||||
expect(formatNumber('1.234;; maximumFractionDigits: 2')).toMatchInlineSnapshot(`"1.23"`);
|
||||
expect(formatNumber(logger, '1.234;; maximumFractionDigits: 2')).toMatchInlineSnapshot(
|
||||
`"1.23"`
|
||||
);
|
||||
});
|
||||
|
||||
it('option minimumSignificantDigits is successful', () => {
|
||||
expect(formatNumber('1;; minimumSignificantDigits: 3')).toMatchInlineSnapshot(`"1.00"`);
|
||||
expect(formatNumber(logger, '1;; minimumSignificantDigits: 3')).toMatchInlineSnapshot(`"1.00"`);
|
||||
});
|
||||
|
||||
it('option maximumSignificantDigits is successful', () => {
|
||||
expect(formatNumber('123456;; maximumSignificantDigits: 4')).toMatchInlineSnapshot(`"123,500"`);
|
||||
expect(formatNumber(logger, '123456;; maximumSignificantDigits: 4')).toMatchInlineSnapshot(
|
||||
`"123,500"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { Logger } from '@kbn/core/server';
|
||||
|
||||
const DEFAULT_LOCALES = ['en-US'];
|
||||
|
||||
|
@ -30,24 +31,28 @@ const DEFAULT_LOCALES = ['en-US'];
|
|||
* @param numberAndFormat string containing a number and formatting options
|
||||
* @returns number formatted according to the options
|
||||
*/
|
||||
export function formatNumber(numberLocalesOptions: string): string {
|
||||
export function formatNumber(logger: Logger, numberLocalesOptions: string): string {
|
||||
const [numString, localesString, optionsString] = splitNumberLocalesOptions(numberLocalesOptions);
|
||||
if (localesString === undefined || optionsString === undefined) {
|
||||
return `invalid format, missing semicolons: '${numberLocalesOptions}'`;
|
||||
return logAndReturnErr(logger, `invalid format, missing semicolons: '${numberLocalesOptions}'`);
|
||||
}
|
||||
|
||||
const num = parseFloat(numString);
|
||||
if (isNaN(num)) return `invalid number: '${numString}'`;
|
||||
if (isNaN(num)) {
|
||||
return logAndReturnErr(logger, `invalid number: '${numString}'`);
|
||||
}
|
||||
|
||||
const locales = getLocales(localesString);
|
||||
|
||||
const [options, optionsError] = getOptions(optionsString);
|
||||
if (optionsError) return `invalid options: ${optionsError}`;
|
||||
if (optionsError) {
|
||||
return logAndReturnErr(logger, `invalid options: ${optionsError}`);
|
||||
}
|
||||
|
||||
try {
|
||||
return new Intl.NumberFormat(locales, options).format(num);
|
||||
} catch (err) {
|
||||
return `error formatting number: ${err.message}`;
|
||||
return logAndReturnErr(logger, `error formatting number: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,3 +115,8 @@ function splitNumberLocalesOptions(
|
|||
const [num, locales, options] = numberLocalesOptions.split(';', 3);
|
||||
return [num.trim(), locales?.trim(), options?.trim()];
|
||||
}
|
||||
|
||||
function logAndReturnErr(logger: Logger, errMessage: string): string {
|
||||
logger.warn(`mustache render error: ${errMessage}`);
|
||||
return errMessage;
|
||||
}
|
||||
|
|
|
@ -59,7 +59,14 @@ export function renderActionParameterTemplatesDefault<RecordType>(
|
|||
params: Record<string, unknown>,
|
||||
variables: Record<string, unknown>
|
||||
) {
|
||||
return renderActionParameterTemplates(undefined, actionTypeId, actionId, params, variables);
|
||||
return renderActionParameterTemplates(
|
||||
logger,
|
||||
undefined,
|
||||
actionTypeId,
|
||||
actionId,
|
||||
params,
|
||||
variables
|
||||
);
|
||||
}
|
||||
|
||||
const createServicesMock = () => {
|
||||
|
|
|
@ -585,7 +585,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
getUnsecuredActionsClient,
|
||||
inMemoryConnectors: this.inMemoryConnectors,
|
||||
renderActionParameterTemplates: (...args) =>
|
||||
renderActionParameterTemplates(actionTypeRegistry, ...args),
|
||||
renderActionParameterTemplates(this.logger, actionTypeRegistry, ...args),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -743,6 +743,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
}
|
||||
|
||||
export function renderActionParameterTemplates<Params extends ActionTypeParams = ActionTypeParams>(
|
||||
logger: Logger,
|
||||
actionTypeRegistry: ActionTypeRegistry | undefined,
|
||||
actionTypeId: string,
|
||||
actionId: string,
|
||||
|
@ -751,8 +752,8 @@ export function renderActionParameterTemplates<Params extends ActionTypeParams =
|
|||
): Params {
|
||||
const actionType = actionTypeRegistry?.get(actionTypeId);
|
||||
if (actionType?.renderParameterTemplates) {
|
||||
return actionType.renderParameterTemplates(params, variables, actionId) as Params;
|
||||
return actionType.renderParameterTemplates(logger, params, variables, actionId) as Params;
|
||||
} else {
|
||||
return renderMustacheObject(params, variables);
|
||||
return renderMustacheObject(logger, params, variables);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,9 +77,9 @@ describe('Registration', () => {
|
|||
const actionId = 'action-id';
|
||||
|
||||
const { renderParameterTemplates } = actionTypeRegistry.register.mock.calls[0][0];
|
||||
const rendered = renderParameterTemplates?.(params, variables, actionId);
|
||||
const rendered = renderParameterTemplates?.(logger, params, variables, actionId);
|
||||
|
||||
expect(mockRenderParameterTemplates).toHaveBeenCalledWith(params, variables, actionId);
|
||||
expect(mockRenderParameterTemplates).toHaveBeenCalledWith(logger, params, variables, actionId);
|
||||
expect(rendered).toBe(renderedVariables);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -119,6 +119,7 @@ export interface ActionValidationService {
|
|||
}
|
||||
|
||||
export type RenderParameterTemplates<Params extends ActionTypeParams> = (
|
||||
logger: Logger,
|
||||
params: Params,
|
||||
variables: Record<string, unknown>,
|
||||
actionId?: string
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { renderParameterTemplates } from './render';
|
||||
import Mustache from 'mustache';
|
||||
|
||||
|
@ -16,16 +17,20 @@ const params = {
|
|||
};
|
||||
|
||||
const variables = { domain: 'm0zepcuuu2' };
|
||||
const logger = loggingSystemMock.createLogger();
|
||||
|
||||
describe('Bedrock - renderParameterTemplates', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
it('should not render body on test action', () => {
|
||||
const testParams = { subAction: 'test', subActionParams: { body: 'test_json' } };
|
||||
const result = renderParameterTemplates(testParams, variables);
|
||||
const result = renderParameterTemplates(logger, testParams, variables);
|
||||
expect(result).toEqual(testParams);
|
||||
});
|
||||
|
||||
it('should rendered body with variables', () => {
|
||||
const result = renderParameterTemplates(params, variables);
|
||||
const result = renderParameterTemplates(logger, params, variables);
|
||||
|
||||
expect(result.subActionParams.body).toEqual(
|
||||
JSON.stringify({
|
||||
|
@ -39,7 +44,7 @@ describe('Bedrock - renderParameterTemplates', () => {
|
|||
jest.spyOn(Mustache, 'render').mockImplementation(() => {
|
||||
throw new Error(errorMessage);
|
||||
});
|
||||
const result = renderParameterTemplates(params, variables);
|
||||
const result = renderParameterTemplates(logger, params, variables);
|
||||
expect(result.subActionParams.body).toEqual(
|
||||
'error rendering mustache template "{"domain":"{{domain}}"}": test error'
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ import { RenderParameterTemplates } from '@kbn/actions-plugin/server/types';
|
|||
import { SUB_ACTION } from '../../../common/bedrock/constants';
|
||||
|
||||
export const renderParameterTemplates: RenderParameterTemplates<ExecutorParams> = (
|
||||
logger,
|
||||
params,
|
||||
variables
|
||||
) => {
|
||||
|
@ -20,7 +21,7 @@ export const renderParameterTemplates: RenderParameterTemplates<ExecutorParams>
|
|||
...params,
|
||||
subActionParams: {
|
||||
...params.subActionParams,
|
||||
body: renderMustacheString(params.subActionParams.body as string, variables, 'json'),
|
||||
body: renderMustacheString(logger, params.subActionParams.body as string, variables, 'json'),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { renderParameterTemplates } from './render';
|
||||
import Mustache from 'mustache';
|
||||
|
||||
|
@ -17,20 +18,25 @@ const params = {
|
|||
},
|
||||
};
|
||||
|
||||
const logger = loggingSystemMock.createLogger();
|
||||
const variables = { domain: 'm0zepcuuu2' };
|
||||
|
||||
describe('D3 Security - renderParameterTemplates', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should not render body on test action', () => {
|
||||
const testParams = {
|
||||
subAction: 'test',
|
||||
subActionParams: { ...params.subActionParams, body: 'test_json' },
|
||||
};
|
||||
const result = renderParameterTemplates(testParams, variables);
|
||||
const result = renderParameterTemplates(logger, testParams, variables);
|
||||
expect(result).toEqual(testParams);
|
||||
});
|
||||
|
||||
it('should rendered body with variables', () => {
|
||||
const result = renderParameterTemplates(params, variables);
|
||||
const result = renderParameterTemplates(logger, params, variables);
|
||||
|
||||
expect(result.subActionParams.body).toEqual(
|
||||
JSON.stringify({
|
||||
|
@ -44,7 +50,7 @@ describe('D3 Security - renderParameterTemplates', () => {
|
|||
jest.spyOn(Mustache, 'render').mockImplementation(() => {
|
||||
throw new Error(errorMessage);
|
||||
});
|
||||
const result = renderParameterTemplates(params, variables);
|
||||
const result = renderParameterTemplates(logger, params, variables);
|
||||
expect(result.subActionParams.body).toEqual(
|
||||
'error rendering mustache template "{"domain":"{{domain}}"}": test error'
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ import { RenderParameterTemplates } from '@kbn/actions-plugin/server/types';
|
|||
import { SUB_ACTION } from '../../../common/d3security/constants';
|
||||
|
||||
export const renderParameterTemplates: RenderParameterTemplates<ExecutorParams> = (
|
||||
logger,
|
||||
params,
|
||||
variables
|
||||
) => {
|
||||
|
@ -20,7 +21,7 @@ export const renderParameterTemplates: RenderParameterTemplates<ExecutorParams>
|
|||
...params,
|
||||
subActionParams: {
|
||||
...params.subActionParams,
|
||||
body: renderMustacheString(params.subActionParams.body as string, variables, 'json'),
|
||||
body: renderMustacheString(logger, params.subActionParams.body as string, variables, 'json'),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -845,7 +845,11 @@ describe('execute()', () => {
|
|||
const variables = {
|
||||
rogue: '*bold*',
|
||||
};
|
||||
const renderedParams = connectorType.renderParameterTemplates!(paramsWithTemplates, variables);
|
||||
const renderedParams = connectorType.renderParameterTemplates!(
|
||||
mockedLogger,
|
||||
paramsWithTemplates,
|
||||
variables
|
||||
);
|
||||
|
||||
expect(renderedParams.message).toBe('\\*bold\\*');
|
||||
expect(renderedParams).toMatchInlineSnapshot(`
|
||||
|
@ -887,7 +891,11 @@ describe('execute()', () => {
|
|||
const variables = {
|
||||
rogue: '*bold*',
|
||||
};
|
||||
const renderedParams = connectorType.renderParameterTemplates!(paramsWithTemplates, variables);
|
||||
const renderedParams = connectorType.renderParameterTemplates!(
|
||||
mockedLogger,
|
||||
paramsWithTemplates,
|
||||
variables
|
||||
);
|
||||
// Yes, this is tested in the snapshot below, but it's double-escaped there,
|
||||
// so easier to see here that the escaping is correct.
|
||||
expect(renderedParams.message).toBe('\\*bold\\*');
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { curry } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import { Logger } from '@kbn/core/server';
|
||||
import nodemailerGetService from 'nodemailer/lib/well-known';
|
||||
import SMTPConnection from 'nodemailer/lib/smtp-connection';
|
||||
import type {
|
||||
|
@ -250,14 +251,15 @@ export function getConnectorType(params: GetConnectorTypeParams): EmailConnector
|
|||
}
|
||||
|
||||
function renderParameterTemplates(
|
||||
logger: Logger,
|
||||
params: ActionParamsType,
|
||||
variables: Record<string, unknown>
|
||||
): ActionParamsType {
|
||||
return {
|
||||
// most of the params need no escaping
|
||||
...renderMustacheObject(params, variables),
|
||||
...renderMustacheObject(logger, params, variables),
|
||||
// message however, needs to escaped as markdown
|
||||
message: renderMustacheString(params.message, variables, 'markdown'),
|
||||
message: renderMustacheString(logger, params.message, variables, 'markdown'),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -371,6 +371,7 @@ describe('execute()', () => {
|
|||
who: 'world',
|
||||
};
|
||||
const renderedParams = connectorType.renderParameterTemplates!(
|
||||
mockedLogger,
|
||||
paramsWithTemplates,
|
||||
variables,
|
||||
'action-type-id'
|
||||
|
@ -397,6 +398,7 @@ describe('execute()', () => {
|
|||
who: 'world',
|
||||
};
|
||||
const renderedParams = connectorType.renderParameterTemplates!(
|
||||
mockedLogger,
|
||||
paramsWithTemplates,
|
||||
variables,
|
||||
'action-type-id'
|
||||
|
@ -446,6 +448,7 @@ describe('execute()', () => {
|
|||
},
|
||||
};
|
||||
const renderedParams = connectorType.renderParameterTemplates!(
|
||||
mockedLogger,
|
||||
paramsWithTemplates,
|
||||
variables,
|
||||
AlertHistoryEsIndexConnectorId
|
||||
|
@ -526,6 +529,7 @@ describe('execute()', () => {
|
|||
},
|
||||
};
|
||||
const renderedParams = connectorType.renderParameterTemplates!(
|
||||
mockedLogger,
|
||||
paramsWithTemplates,
|
||||
variables,
|
||||
AlertHistoryEsIndexConnectorId
|
||||
|
@ -583,6 +587,7 @@ describe('execute()', () => {
|
|||
|
||||
expect(() =>
|
||||
connectorType.renderParameterTemplates!(
|
||||
mockedLogger,
|
||||
paramsWithTemplates,
|
||||
variables,
|
||||
AlertHistoryEsIndexConnectorId
|
||||
|
|
|
@ -160,11 +160,16 @@ async function executor(
|
|||
}
|
||||
|
||||
function renderParameterTemplates(
|
||||
logger: Logger,
|
||||
params: ActionParamsType,
|
||||
variables: Record<string, unknown>,
|
||||
actionId?: string
|
||||
): ActionParamsType {
|
||||
const { documents, indexOverride } = renderMustacheObject<ActionParamsType>(params, variables);
|
||||
const { documents, indexOverride } = renderMustacheObject<ActionParamsType>(
|
||||
logger,
|
||||
params,
|
||||
variables
|
||||
);
|
||||
|
||||
if (actionId === AlertHistoryEsIndexConnectorId) {
|
||||
const alertHistoryDoc = buildAlertHistoryDocument(variables);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { renderParameterTemplates } from './render';
|
||||
import Mustache from 'mustache';
|
||||
|
||||
|
@ -16,16 +17,17 @@ const params = {
|
|||
};
|
||||
|
||||
const variables = { domain: 'm0zepcuuu2' };
|
||||
const logger = loggingSystemMock.createLogger();
|
||||
|
||||
describe('OpenAI - renderParameterTemplates', () => {
|
||||
it('should not render body on test action', () => {
|
||||
const testParams = { subAction: 'test', subActionParams: { body: 'test_json' } };
|
||||
const result = renderParameterTemplates(testParams, variables);
|
||||
const result = renderParameterTemplates(logger, testParams, variables);
|
||||
expect(result).toEqual(testParams);
|
||||
});
|
||||
|
||||
it('should rendered body with variables', () => {
|
||||
const result = renderParameterTemplates(params, variables);
|
||||
const result = renderParameterTemplates(logger, params, variables);
|
||||
|
||||
expect(result.subActionParams.body).toEqual(
|
||||
JSON.stringify({
|
||||
|
@ -39,7 +41,7 @@ describe('OpenAI - renderParameterTemplates', () => {
|
|||
jest.spyOn(Mustache, 'render').mockImplementation(() => {
|
||||
throw new Error(errorMessage);
|
||||
});
|
||||
const result = renderParameterTemplates(params, variables);
|
||||
const result = renderParameterTemplates(logger, params, variables);
|
||||
expect(result.subActionParams.body).toEqual(
|
||||
'error rendering mustache template "{"domain":"{{domain}}"}": test error'
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ import { RenderParameterTemplates } from '@kbn/actions-plugin/server/types';
|
|||
import { SUB_ACTION } from '../../../common/openai/constants';
|
||||
|
||||
export const renderParameterTemplates: RenderParameterTemplates<ExecutorParams> = (
|
||||
logger,
|
||||
params,
|
||||
variables
|
||||
) => {
|
||||
|
@ -20,7 +21,7 @@ export const renderParameterTemplates: RenderParameterTemplates<ExecutorParams>
|
|||
...params,
|
||||
subActionParams: {
|
||||
...params.subActionParams,
|
||||
body: renderMustacheString(params.subActionParams.body as string, variables, 'json'),
|
||||
body: renderMustacheString(logger, params.subActionParams.body as string, variables, 'json'),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { OpsgenieSubActions } from '../../../common';
|
||||
import { renderParameterTemplates } from './render_template_variables';
|
||||
|
||||
const ruleTagsTemplate = '{{rule.tags}}';
|
||||
const logger = loggingSystemMock.createLogger();
|
||||
|
||||
describe('renderParameterTemplates', () => {
|
||||
const variables = {
|
||||
|
@ -20,6 +22,7 @@ describe('renderParameterTemplates', () => {
|
|||
it('renders the rule.tags as a single string if subAction is not set to CreateAlert', () => {
|
||||
expect(
|
||||
renderParameterTemplates(
|
||||
logger,
|
||||
{
|
||||
subAction: '',
|
||||
subActionParams: {
|
||||
|
@ -43,6 +46,7 @@ describe('renderParameterTemplates', () => {
|
|||
it('does not transform the tags if the rule.tags string is not found', () => {
|
||||
expect(
|
||||
renderParameterTemplates(
|
||||
logger,
|
||||
{
|
||||
subAction: OpsgenieSubActions.CreateAlert,
|
||||
subActionParams: {
|
||||
|
@ -66,6 +70,7 @@ describe('renderParameterTemplates', () => {
|
|||
it('transforms the rule.tags to an empty array when the field does not exist in the variable', () => {
|
||||
expect(
|
||||
renderParameterTemplates(
|
||||
logger,
|
||||
{
|
||||
subAction: OpsgenieSubActions.CreateAlert,
|
||||
subActionParams: {
|
||||
|
@ -87,6 +92,7 @@ describe('renderParameterTemplates', () => {
|
|||
it('does not transform the tags when the field does not exist in the params', () => {
|
||||
expect(
|
||||
renderParameterTemplates(
|
||||
logger,
|
||||
{
|
||||
subAction: OpsgenieSubActions.CreateAlert,
|
||||
subActionParams: {},
|
||||
|
@ -104,6 +110,7 @@ describe('renderParameterTemplates', () => {
|
|||
it('replaces the rule.tags template with an array of strings', () => {
|
||||
expect(
|
||||
renderParameterTemplates(
|
||||
logger,
|
||||
{
|
||||
subAction: OpsgenieSubActions.CreateAlert,
|
||||
subActionParams: {
|
||||
|
@ -132,6 +139,7 @@ describe('renderParameterTemplates', () => {
|
|||
it('replaces the rule.tags template with only a single instance of the rule.tags even when the mustache template exists multiple times', () => {
|
||||
expect(
|
||||
renderParameterTemplates(
|
||||
logger,
|
||||
{
|
||||
subAction: OpsgenieSubActions.CreateAlert,
|
||||
subActionParams: {
|
||||
|
@ -161,6 +169,7 @@ describe('renderParameterTemplates', () => {
|
|||
it('replaces the rule.tags template with empty array and preserves the other values already in the array', () => {
|
||||
expect(
|
||||
renderParameterTemplates(
|
||||
logger,
|
||||
{
|
||||
subAction: OpsgenieSubActions.CreateAlert,
|
||||
subActionParams: {
|
||||
|
@ -186,6 +195,7 @@ describe('renderParameterTemplates', () => {
|
|||
it('replaces the rule.tags template with variable value when the path is a full string', () => {
|
||||
expect(
|
||||
renderParameterTemplates(
|
||||
logger,
|
||||
{
|
||||
subAction: OpsgenieSubActions.CreateAlert,
|
||||
subActionParams: {
|
||||
|
@ -212,6 +222,7 @@ describe('renderParameterTemplates', () => {
|
|||
it('replaces the rule.tags template and other templates', () => {
|
||||
expect(
|
||||
renderParameterTemplates(
|
||||
logger,
|
||||
{
|
||||
subAction: OpsgenieSubActions.CreateAlert,
|
||||
subActionParams: {
|
||||
|
|
|
@ -15,17 +15,18 @@ import { OpsgenieSubActions } from '../../../common';
|
|||
import { CreateAlertSubActionParams } from './types';
|
||||
|
||||
export const renderParameterTemplates: RenderParameterTemplates<ExecutorParams> = (
|
||||
logger,
|
||||
params,
|
||||
variables
|
||||
) => {
|
||||
if (!isCreateAlertSubAction(params) || !params.subActionParams.tags) {
|
||||
return renderMustacheObject(params, variables);
|
||||
return renderMustacheObject(logger, params, variables);
|
||||
}
|
||||
|
||||
const foundRuleTagsTemplate = params.subActionParams.tags.includes(RULE_TAGS_TEMPLATE);
|
||||
|
||||
if (!foundRuleTagsTemplate) {
|
||||
return renderMustacheObject(params, variables);
|
||||
return renderMustacheObject(logger, params, variables);
|
||||
}
|
||||
|
||||
const paramsCopy = cloneDeep(params);
|
||||
|
@ -39,7 +40,7 @@ export const renderParameterTemplates: RenderParameterTemplates<ExecutorParams>
|
|||
...getRuleTags(variables),
|
||||
]);
|
||||
|
||||
return renderMustacheObject(paramsCopy, variables);
|
||||
return renderMustacheObject(logger, paramsCopy, variables);
|
||||
};
|
||||
|
||||
type CreateAlertParams = CreateAlertSubActionParams & Record<string, unknown>;
|
||||
|
|
|
@ -9,6 +9,7 @@ import { map } from 'lodash';
|
|||
import { set } from '@kbn/safer-lodash-set/fp';
|
||||
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
|
||||
import { ExecutorParams } from '@kbn/actions-plugin/server/sub_action_framework/types';
|
||||
import { Logger } from '@kbn/core/server';
|
||||
import { SUB_ACTION } from '../../../common/sentinelone/constants';
|
||||
|
||||
interface Context {
|
||||
|
@ -16,6 +17,7 @@ interface Context {
|
|||
}
|
||||
|
||||
export const renderParameterTemplates = (
|
||||
logger: Logger,
|
||||
params: ExecutorParams,
|
||||
variables: Record<string, unknown>
|
||||
) => {
|
||||
|
|
|
@ -344,7 +344,11 @@ describe('execute()', () => {
|
|||
const variables = {
|
||||
rogue: '*bold*',
|
||||
};
|
||||
const params = connectorType.renderParameterTemplates!(paramsWithTemplates, variables);
|
||||
const params = connectorType.renderParameterTemplates!(
|
||||
mockedLogger,
|
||||
paramsWithTemplates,
|
||||
variables
|
||||
);
|
||||
expect(params.message).toBe('`*bold*`');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import { URL } from 'url';
|
|||
import HttpProxyAgent from 'http-proxy-agent';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Logger } from '@kbn/core/server';
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import { IncomingWebhook, IncomingWebhookResult } from '@slack/webhook';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
|
@ -94,11 +95,12 @@ export function getConnectorType({
|
|||
}
|
||||
|
||||
function renderParameterTemplates(
|
||||
logger: Logger,
|
||||
params: ActionParamsType,
|
||||
variables: Record<string, unknown>
|
||||
): ActionParamsType {
|
||||
return {
|
||||
message: renderMustacheString(params.message, variables, 'slack'),
|
||||
message: renderMustacheString(logger, params.message, variables, 'slack'),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -240,6 +240,7 @@ describe('execute', () => {
|
|||
};
|
||||
const variables = { injected: '*foo*' };
|
||||
const params = connectorType.renderParameterTemplates!(
|
||||
mockedLogger,
|
||||
paramsWithTemplates,
|
||||
variables
|
||||
) as PostMessageParams;
|
||||
|
@ -266,6 +267,7 @@ describe('execute', () => {
|
|||
};
|
||||
const variables = { name: '"Dwight"' };
|
||||
const params = connectorType.renderParameterTemplates!(
|
||||
mockedLogger,
|
||||
paramsWithTemplates,
|
||||
variables
|
||||
) as PostMessageParams;
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
AlertingConnectorFeatureId,
|
||||
SecurityConnectorFeatureId,
|
||||
} from '@kbn/actions-plugin/common/types';
|
||||
import { Logger } from '@kbn/core/server';
|
||||
import { renderMustacheString } from '@kbn/actions-plugin/server/lib/mustache_renderer';
|
||||
import type { ValidatorServices } from '@kbn/actions-plugin/server/types';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -69,13 +70,17 @@ const validateSlackUrl = (secretsObject: SlackApiSecrets, validatorServices: Val
|
|||
}
|
||||
};
|
||||
|
||||
const renderParameterTemplates = (params: SlackApiParams, variables: Record<string, unknown>) => {
|
||||
const renderParameterTemplates = (
|
||||
logger: Logger,
|
||||
params: SlackApiParams,
|
||||
variables: Record<string, unknown>
|
||||
) => {
|
||||
if (params.subAction === 'postMessage') {
|
||||
return {
|
||||
subAction: params.subAction,
|
||||
subActionParams: {
|
||||
...params.subActionParams,
|
||||
text: renderMustacheString(params.subActionParams.text, variables, 'slack'),
|
||||
text: renderMustacheString(logger, params.subActionParams.text, variables, 'slack'),
|
||||
},
|
||||
};
|
||||
} else if (params.subAction === 'postBlockkit') {
|
||||
|
@ -83,7 +88,7 @@ const renderParameterTemplates = (params: SlackApiParams, variables: Record<stri
|
|||
subAction: params.subAction,
|
||||
subActionParams: {
|
||||
...params.subActionParams,
|
||||
text: renderMustacheString(params.subActionParams.text, variables, 'json'),
|
||||
text: renderMustacheString(logger, params.subActionParams.text, variables, 'json'),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { set } from '@kbn/safer-lodash-set/fp';
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { renderParameterTemplates } from './render';
|
||||
|
||||
const params = {
|
||||
|
@ -15,6 +16,7 @@ const params = {
|
|||
},
|
||||
};
|
||||
|
||||
const logger = loggingSystemMock.createLogger();
|
||||
const alert1Expected = {
|
||||
_id: 'l8jb2e55f2740ab15ba4e2717a96c93396c4ce323ac7a486c7dc179d67rg3ft',
|
||||
_index: '.internal.alerts-security.alerts-default-000001',
|
||||
|
@ -59,12 +61,12 @@ describe('Tines body render', () => {
|
|||
describe('renderParameterTemplates', () => {
|
||||
it('should not render body on test action', () => {
|
||||
const testParams = { subAction: 'test', subActionParams: { body: 'test_json' } };
|
||||
const result = renderParameterTemplates(testParams, variables);
|
||||
const result = renderParameterTemplates(logger, testParams, variables);
|
||||
expect(result).toEqual(testParams);
|
||||
});
|
||||
|
||||
it('should rendered body from variables with cleaned alerts on run action', () => {
|
||||
const result = renderParameterTemplates(params, variables);
|
||||
const result = renderParameterTemplates(logger, params, variables);
|
||||
|
||||
expect(result.subActionParams.body).toEqual(
|
||||
JSON.stringify({
|
||||
|
@ -79,14 +81,14 @@ describe('Tines body render', () => {
|
|||
|
||||
it('should rendered body from variables on run action without context.alerts', () => {
|
||||
const variablesWithoutAlerts = set('context.alerts', undefined, variables);
|
||||
const result = renderParameterTemplates(params, variablesWithoutAlerts);
|
||||
const result = renderParameterTemplates(logger, params, variablesWithoutAlerts);
|
||||
|
||||
expect(result.subActionParams.body).toEqual(JSON.stringify(variablesWithoutAlerts));
|
||||
});
|
||||
|
||||
it('should rendered body from variables on run action without context', () => {
|
||||
const variablesWithoutContext = set('context', undefined, variables);
|
||||
const result = renderParameterTemplates(params, variablesWithoutContext);
|
||||
const result = renderParameterTemplates(logger, params, variablesWithoutContext);
|
||||
|
||||
expect(result.subActionParams.body).toEqual(JSON.stringify(variablesWithoutContext));
|
||||
});
|
||||
|
@ -96,7 +98,7 @@ describe('Tines body render', () => {
|
|||
jest.spyOn(JSON, 'stringify').mockImplementationOnce(() => {
|
||||
throw new Error(errorMessage);
|
||||
});
|
||||
const result = renderParameterTemplates(params, variables);
|
||||
const result = renderParameterTemplates(logger, params, variables);
|
||||
expect(result.subActionParams.body).toEqual(
|
||||
JSON.stringify({ error: { message: errorMessage } })
|
||||
);
|
||||
|
|
|
@ -15,6 +15,7 @@ interface Context {
|
|||
}
|
||||
|
||||
export const renderParameterTemplates: RenderParameterTemplates<ExecutorParams> = (
|
||||
logger,
|
||||
params,
|
||||
variables
|
||||
) => {
|
||||
|
|
|
@ -227,7 +227,11 @@ describe('execute Torq action', () => {
|
|||
scalar: '1970',
|
||||
scalar_with_json_chars: 'noinjection", "here": "',
|
||||
};
|
||||
const params = actionType.renderParameterTemplates!(paramsWithTemplates, variables);
|
||||
const params = actionType.renderParameterTemplates!(
|
||||
mockedLogger,
|
||||
paramsWithTemplates,
|
||||
variables
|
||||
);
|
||||
expect(params.body).toBe(
|
||||
`{"x": ${templatedObject}, "y": "${variables.scalar}", "z": "${variables.scalar_with_json_chars}"}`
|
||||
);
|
||||
|
|
|
@ -88,11 +88,12 @@ export function getActionType(): TorqActionType {
|
|||
}
|
||||
|
||||
function renderParameterTemplates(
|
||||
logger: Logger,
|
||||
params: ActionParamsType,
|
||||
variables: Record<string, unknown>
|
||||
): ActionParamsType {
|
||||
if (!params.body) return params;
|
||||
return renderMustacheObject(params, variables);
|
||||
return renderMustacheObject(logger, params, variables);
|
||||
}
|
||||
|
||||
function validateActionTypeConfig(
|
||||
|
|
|
@ -676,7 +676,11 @@ describe('execute()', () => {
|
|||
const variables = {
|
||||
rogue,
|
||||
};
|
||||
const params = connectorType.renderParameterTemplates!(paramsWithTemplates, variables);
|
||||
const params = connectorType.renderParameterTemplates!(
|
||||
mockedLogger,
|
||||
paramsWithTemplates,
|
||||
variables
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let paramsObject: any;
|
||||
|
|
|
@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { isString } from 'lodash';
|
||||
import axios, { AxiosError, AxiosResponse } from 'axios';
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import { Logger } from '@kbn/core/server';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { map, getOrElse } from 'fp-ts/lib/Option';
|
||||
import type {
|
||||
|
@ -139,12 +140,13 @@ export function getConnectorType(): WebhookConnectorType {
|
|||
}
|
||||
|
||||
function renderParameterTemplates(
|
||||
logger: Logger,
|
||||
params: ActionParamsType,
|
||||
variables: Record<string, unknown>
|
||||
): ActionParamsType {
|
||||
if (!params.body) return params;
|
||||
return {
|
||||
body: renderMustacheString(params.body, variables, 'json'),
|
||||
body: renderMustacheString(logger, params.body, variables, 'json'),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue