Workflowでは、Webhookやメールの設定の中でテンプレートを使ってカスタムメッセージを作成していただくことができます。
また、Webhookの場合、簡単なテンプレートであれば設定画面内のPreviewを使って確認していただくことができますが、手の込んだことになると、実際にIssueを発生させながら確認したりと手間がかかってしまいます。

ドキュメントに記載されていますが、メッセージテンプレートでは、Handlebarsの組み込みヘルパーがサポートされています。また、Handlebarsではplaygroundが提供されているので、そちらを活用したデバッグ方法をご紹介します。

Handlebarsのplaygroundについて

playgroundにアクセスしていただくと、以下のような画面が表示されると思います。

この画面内の4つのエリアを上から順に簡単に説明します。

Template

Workflowで設定していただくメッセージテンプレートと同様の内容を記載する欄になります。本ブログでは、こちらの欄に入力される内容を試行錯誤していただける方法をご紹介します。

Preparation-Script

独自の処理の追加などがある場合、こちらの欄に追記ができます。New RelicのWorkflowでは、JSONなどいくつかの追加でヘルパー関数を定義しています。これらのNew Relicで追加したヘルパー関数と同等と思われる処理を行うコードを後述します。

Input

入力データです。Workflowで利用可能なデータをinputとして利用できます。New RelicのWorkflowではIssue発生時等にこのデータが生成されます。実際のデータをもとにデバッグしていただくため、実際にIssueを発生させて、この入力データとして利用可能なデータの取得方法を後述します。

Output

上記の設定やデータから生成された出力データです。helper関数を経由したデータは、残念ながら自動でhtmlエスケープされるようです。このため、完全にWorkflowでの出力と同等にはならないようです。こちら、本playgroundを利用する上での制限事項として、ご了承ください。

実際のIssueからのInputデータの取得方法

対象のアラートに対応するWorkflowの設定にて、メールの通知先を作成し、Custom Detailsに以下の内容を設定して保存してください。

{{ json this }}

その後、対象のWorkflow経由で通知が送られるように、対象のアラートコンディションで違反を発生させてIssueを発生させて見てください。すると以下のようなJSONテキストがCustom Detailsに含まれたメールを受信することができるかと思います。

 

このJSONテキストがInputとして利用できます。JSON全体をコピーして、Input欄へペーストしてください。

追加ヘルパー関数の登録

前述しましたが、New Relicでは独自に追加したヘルパー関数もあります。それとほぼ同等と思われるコードを以下に記載します。なお、以下の制限があることご了承ください。

  • ドキュメント記載の仕様や実動作をもとに作成したコードのため、実際の実装とは異なる可能性があります。最終的には、New Relic上での動作確認をおすすめします。
  • timezoneヘルパーに関しては、コード簡易化のため、実際の仕様とはフォーマットが異なります。
Handlebars.registerHelper('json', function (obj) {
    return JSON.stringify(obj);
});

const returnResultForConditionalHelper = (result, opt, self) => {
    if (result) {
        if ('fn' in opt) {
            return opt.fn(self);
        }
        else if ('yes' in opt.hash) {
            return opt.hash['yes'];
        }
        else {
			return 'true';
        }
    } else {
        if ('inverse' in opt) {
            return opt.inverse(self);
        }
        else if ('no' in opt.hash) {
            return opt.hash['no'];
        }
        else {
			return 'false';
        }
    }
};

Handlebars.registerHelper("eq", function (c1, c2, opt) {
    return returnResultForConditionalHelper(c1 == c2, opt, this);
});

Handlebars.registerHelper("contains", function (c1, c2, opt) {
    return returnResultForConditionalHelper(c2.includes(c1), opt, this);
});

Handlebars.registerHelper("math", function (c1, c2, c3) {
    var v1 = Number(c1), v2 = Number(c3);
    if (!isNaN(v1) && !isNaN(v2)) {
        switch (c2) {
            case '+':
                return v1 + v2;
            case '-':
                return v1 - v2;
            case '*':
                return v1 * v2;
            case '/':
                return v1 / v2;
            default:
                return null;
        }
    } else {
        return null;
    }
});

Handlebars.registerHelper("timezone", function (time, tzString) {
    return new Date(Number(time)).toLocaleString("en-US", {timeZone: tzString});
});


/* https://stackoverflow.com/questions/874709/converting-user-input-string-to-regular-expression */
const stringToRegex = str => {
    try {
        /* Main regex */
        const main = str.match(/\/(.+)\/.*/)[1];
        /* Regex options */
        const options = str.match(/\/.+\/(.*)/)[1];
        /* Compiled regex */
        return new RegExp(main, options);
    } catch (ignore) {
        return str;
    }
};


Handlebars.registerHelper("replace", function (c1, c2, opt) {
    const re = stringToRegex(c1);
    if (c2.match(re)) {
        return c2.replace(re, opt.fn(this));
    } else {
        return opt.inverse(this);
    }
});

なお、実際にplaygroundで試す場合、上記コードでは、行数が多く見づらい点があると思います。以下に1ラインで記載したコードも記述します。実際にはこちらを利用された方が良いかと思われます。

Handlebars.registerHelper('json', function (obj) { return JSON.stringify(obj); }); const returnResultForConditionalHelper = (result, opt, self) => { if (result) { if ('fn' in opt) { return opt.fn(self); } else if ('yes' in opt.hash) { return opt.hash['yes']; } else { return 'true'; } } else { if ('inverse' in opt) { return opt.inverse(self); } else if ('no' in opt.hash) { return opt.hash['no']; } else { return 'false'; } } }; Handlebars.registerHelper("eq", function (c1, c2, opt) { return returnResultForConditionalHelper(c1 == c2, opt, this); }); Handlebars.registerHelper("contains", function (c1, c2, opt) { return returnResultForConditionalHelper(c2.includes(c1), opt, this); }); Handlebars.registerHelper("math", function (c1, c2, c3) { var v1 = Number(c1), v2 = Number(c3); if (!isNaN(v1) && !isNaN(v2)) { switch (c2) { case '+': return v1 + v2; case '-': return v1 - v2; case '*': return v1 * v2; case '/': return v1 / v2; default: return null; } } else { return null; } }); Handlebars.registerHelper("timezone", function (time, tzString) { return new Date(Number(time)).toLocaleString("en-US", {timeZone: tzString}); }); /* https://stackoverflow.com/questions/874709/converting-user-input-string-to-regular-expression */ const stringToRegex = str => { try { /* Main regex */ const main = str.match(/\/(.+)\/.*/)[1]; /* Regex options */ const options = str.match(/\/.+\/(.*)/)[1]; /* Compiled regex */ return new RegExp(main, options); } catch (ignore) { return str; } }; Handlebars.registerHelper("replace", function (c1, c2, opt) { const re = stringToRegex(c1); if (c2.match(re)) { return c2.replace(re, opt.fn(this)); } else { return opt.inverse(this); } });

なお、こちらのリンクをクリックしていただくと、上記ヘルパー関数を反映したplaygroundの画面へ遷移していただけると思います。

以上で、playgroundを使った試行錯誤の準備ができました。Template欄にお客様が記載したい内容を記載していただき、期待通りの出力が得られるまで試行錯誤してみてください。こちらで試行錯誤してみていただき、問題なさそうであれば、実際のWorkflowを使って最終確認を行っていただくのが良いかと思われます。

制限事項

Outputの内容がhtmlエスケープされた内容になっている。

helper関数を使って出力された文字列等に関しまして、残念ながら、htmlエスケープされてしまうようです。こちら、Workflowの動作とは異なりますが、ご了承ください。

timezoneヘルパーの結果がNew Relicのドキュメントのフォーマットと異なる

コードの簡略化のためとなります。ご了承ください。

上記以外の動作で、実際のWorkflowとは異なる

追加ヘルパー関数に関して、同等と思われる実装のため、多少異なる可能性があります。お気づきの点がございましたら、担当の営業等にご連絡いただければ幸いでございます。