-1))return a;m=""}else m=c.gUrls[l],e.helper.isUndefined(c.gTitles[l])||(n=c.gTitles[l]);m=m.replace(e.helper.regexes.asteriskAndDash,e.helper.escapeCharactersCallback);var o='"+k+""};return a=a.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)][ ]?(?:\n[ ]*)?\[(.*?)])()()()()/g,d),a=a.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)]\([ \t]*()(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,d),a=a.replace(/(\[([^\[\]]+)])()()()()()/g,d),b.ghMentions&&(a=a.replace(/(^|\s)(\\)?(@([a-z\d\-]+))(?=[.!?;,[\]()]|\s|$)/gim,function(a,c,d,f,g){if("\\"===d)return c+f;if(!e.helper.isString(b.ghMentionsLink))throw new Error("ghMentionsLink option must be a string");var h=b.ghMentionsLink.replace(/\{u}/g,g);return c+''+f+""})),a=c.converter._dispatch("anchors.after",a,b,c)}),e.subParser("autoLinks",function(a,b,c){"use strict";function d(a,c,d,e,f){var g=c,h="";return/^www\./i.test(c)&&(c=c.replace(/^www\./i,"http://www.")),b.excludeTrailingPunctuationFromURLs&&f&&(h=f),''+g+""+h}function f(a,c,d){var f="mailto:";return c=c||"",d=e.subParser("unescapeSpecialChars")(d),b.encodeEmails?(d=e.helper.encodeEmailAddress(d),f=e.helper.encodeEmailAddress(f+d)):f+=d,c+''+d+""}a=c.converter._dispatch("autoLinks.before",a,b,c);var g=/\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)()(?=\s|$)(?!["<>])/gi,h=/\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+?)([.!?()]?)(?=\s|$)(?!["<>])/gi,i=/<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi,j=/(^|\s)(?:mailto:)?([A-Za-z0-9!#$%&'*+-\/=?^_`{|}~.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?=$|\s)/gim,k=/<()(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi;return a=a.replace(i,d),a=a.replace(k,f),b.simplifiedAutoLink&&(a=b.excludeTrailingPunctuationFromURLs?a.replace(h,d):a.replace(g,d),a=a.replace(j,f)),a=c.converter._dispatch("autoLinks.after",a,b,c)}),e.subParser("blockGamut",function(a,b,c){"use strict";return a=c.converter._dispatch("blockGamut.before",a,b,c),a=e.subParser("blockQuotes")(a,b,c),a=e.subParser("headers")(a,b,c),a=e.subParser("horizontalRule")(a,b,c),a=e.subParser("lists")(a,b,c),a=e.subParser("codeBlocks")(a,b,c),a=e.subParser("tables")(a,b,c),a=e.subParser("hashHTMLBlocks")(a,b,c),a=e.subParser("paragraphs")(a,b,c),a=c.converter._dispatch("blockGamut.after",a,b,c)}),e.subParser("blockQuotes",function(a,b,c){"use strict";return a=c.converter._dispatch("blockQuotes.before",a,b,c),a=a.replace(/((^ {0,3}>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(a,d){var f=d;return f=f.replace(/^[ \t]*>[ \t]?/gm,"¨0"),f=f.replace(/¨0/g,""),f=f.replace(/^[ \t]+$/gm,""),f=e.subParser("githubCodeBlocks")(f,b,c),f=e.subParser("blockGamut")(f,b,c),f=f.replace(/(^|\n)/g,"$1 "),f=f.replace(/(\s*[^\r]+?<\/pre>)/gm,function(a,b){var c=b;return c=c.replace(/^ /gm,"¨0"),c=c.replace(/¨0/g,"")}),e.subParser("hashBlock")("\n"+f+"\n
",b,c)}),a=c.converter._dispatch("blockQuotes.after",a,b,c)}),e.subParser("codeBlocks",function(a,b,c){"use strict";a=c.converter._dispatch("codeBlocks.before",a,b,c),a+="¨0";var d=/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=¨0))/g;return a=a.replace(d,function(a,d,f){var g=d,h=f,i="\n";return g=e.subParser("outdent")(g),g=e.subParser("encodeCode")(g),g=e.subParser("detab")(g),g=g.replace(/^\n+/g,""),g=g.replace(/\n+$/g,""),b.omitExtraWLInCodeBlocks&&(i=""),g=""+g+i+"
",e.subParser("hashBlock")(g,b,c)+h}),a=a.replace(/¨0/,""),a=c.converter._dispatch("codeBlocks.after",a,b,c)}),e.subParser("codeSpans",function(a,b,c){"use strict";return a=c.converter._dispatch("codeSpans.before",a,b,c),"undefined"==typeof a&&(a=""),a=a.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(a,b,c,d){var f=d;return f=f.replace(/^([ \t]*)/g,""),f=f.replace(/[ \t]*$/g,""),f=e.subParser("encodeCode")(f),b+""+f+""}),a=c.converter._dispatch("codeSpans.after",a,b,c)}),e.subParser("detab",function(a){"use strict";return a=a.replace(/\t(?=\t)/g," "),a=a.replace(/\t/g,"¨A¨B"),a=a.replace(/¨B(.+?)¨A/g,function(a,b){for(var c=b,d=4-c.length%4,e=0;d>e;e++)c+=" ";return c}),a=a.replace(/¨A/g," "),a=a.replace(/¨B/g,"")}),e.subParser("encodeAmpsAndAngles",function(a){"use strict";return a=a.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"),a=a.replace(/<(?![a-z\/?\$!])/gi,"<")}),e.subParser("encodeBackslashEscapes",function(a){"use strict";return a=a.replace(/\\(\\)/g,e.helper.escapeCharactersCallback),a=a.replace(/\\([`*_{}\[\]()>#+-.!~])/g,e.helper.escapeCharactersCallback)}),e.subParser("encodeCode",function(a){"use strict";return a=a.replace(/&/g,"&").replace(//g,">").replace(/([*_{}\[\]\\])/g,e.helper.escapeCharactersCallback)}),e.subParser("escapeSpecialCharsWithinTagAttributes",function(a){"use strict";var b=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi;return a=a.replace(b,function(a){return a.replace(/(.)<\/?code>(?=.)/g,"$1`").replace(/([\\`*_ ~=])/g,e.helper.escapeCharactersCallback)})}),e.subParser("githubCodeBlocks",function(a,b,c){"use strict";return b.ghCodeBlocks?(a=c.converter._dispatch("githubCodeBlocks.before",a,b,c),a+="¨0",a=a.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g,function(a,d,f){var g=b.omitExtraWLInCodeBlocks?"":"\n";return f=e.subParser("encodeCode")(f),f=e.subParser("detab")(f),f=f.replace(/^\n+/g,""),f=f.replace(/\n+$/g,""),f=""+f+g+"
",f=e.subParser("hashBlock")(f,b,c),"\n\n¨G"+(c.ghCodeBlocks.push({text:a,codeblock:f})-1)+"G\n\n"}),a=a.replace(/¨0/,""),c.converter._dispatch("githubCodeBlocks.after",a,b,c)):a}),e.subParser("hashBlock",function(a,b,c){"use strict";return a=a.replace(/(^\n+|\n+$)/g,""),"\n\n¨K"+(c.gHtmlBlocks.push(a)-1)+"K\n\n"}),e.subParser("hashElement",function(a,b,c){"use strict";return function(a,b){var d=b;return d=d.replace(/\n\n/g,"\n"),d=d.replace(/^\n/,""),d=d.replace(/\n+$/g,""),d="\n\n¨K"+(c.gHtmlBlocks.push(d)-1)+"K\n\n"}}),e.subParser("hashHTMLBlocks",function(a,b,c){"use strict";for(var d=["pre","div","h1","h2","h3","h4","h5","h6","blockquote","table","dl","ol","ul","script","noscript","form","fieldset","iframe","math","style","section","header","footer","nav","article","aside","address","audio","canvas","figure","hgroup","output","video","p"],f=function(a,b,d,e){var f=a;return-1!==d.search(/\bmarkdown\b/)&&(f=d+c.converter.makeHtml(b)+e),"\n\n¨K"+(c.gHtmlBlocks.push(f)-1)+"K\n\n"},g=0;g]*>",""+d[g]+">","gim");return a=a.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,e.subParser("hashElement")(a,b,c)),a=e.helper.replaceRecursiveRegExp(a,function(a){return"\n\n¨K"+(c.gHtmlBlocks.push(a)-1)+"K\n\n"},"^ {0,3}","gm"),a=a.replace(/(?:\n\n)( {0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,e.subParser("hashElement")(a,b,c))}),e.subParser("hashHTMLSpans",function(a,b,c){"use strict";for(var d=e.helper.matchRecursiveRegExp(a,"]*>","","gi"),f=0;f]*>\\s*]*>","^ {0,3}\\s*","gim")}),e.subParser("headers",function(a,b,c){"use strict";function d(a){var b,d;return d=h?a.replace(/ /g,"-").replace(/&/g,"").replace(/¨T/g,"").replace(/¨D/g,"").replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g,"").toLowerCase():a.replace(/[^\w]/g,"").toLowerCase(),c.hashLinkCounts[d]?b=d+"-"+c.hashLinkCounts[d]++:(b=d,c.hashLinkCounts[d]=1),f===!0&&(f="section"),e.helper.isString(f)?f+b:b}a=c.converter._dispatch("headers.before",a,b,c);var f=b.prefixHeaderId,g=isNaN(parseInt(b.headerLevelStart))?1:parseInt(b.headerLevelStart),h=b.ghCompatibleHeaderId,i=b.smoothLivePreview?/^(.+)[ \t]*\n={2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n=+[ \t]*\n+/gm,j=b.smoothLivePreview?/^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n-+[ \t]*\n+/gm;a=a.replace(i,function(a,f){var h=e.subParser("spanGamut")(f,b,c),i=b.noHeaderId?"":' id="'+d(f)+'"',j=g,k=""+h+"";return e.subParser("hashBlock")(k,b,c)}),a=a.replace(j,function(a,f){var h=e.subParser("spanGamut")(f,b,c),i=b.noHeaderId?"":' id="'+d(f)+'"',j=g+1,k=""+h+"";return e.subParser("hashBlock")(k,b,c)});var k=b.requireSpaceBeforeHeadingText?/^(#{1,6})[ \t]+(.+?)[ \t]*#*\n+/gm:/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm;return a=a.replace(k,function(a,f,h){var i=e.subParser("spanGamut")(h,b,c),j=b.noHeaderId?"":' id="'+d(h)+'"',k=g-1+f.length,l=""+i+"";return e.subParser("hashBlock")(l,b,c)}),a=c.converter._dispatch("headers.after",a,b,c)}),e.subParser("horizontalRule",function(a,b,c){"use strict";a=c.converter._dispatch("horizontalRule.before",a,b,c);var d=e.subParser("hashBlock")("
",b,c);return a=a.replace(/^ {0,2}( ?-){3,}[ \t]*$/gm,d),a=a.replace(/^ {0,2}( ?\*){3,}[ \t]*$/gm,d),a=a.replace(/^ {0,2}( ?_){3,}[ \t]*$/gm,d),a=c.converter._dispatch("horizontalRule.after",a,b,c)}),e.subParser("images",function(a,b,c){"use strict";function d(a,b,d,f,g,h,i,j){var k=c.gUrls,l=c.gTitles,m=c.gDimensions;if(d=d.toLowerCase(),j||(j=""),""===f||null===f){if((""===d||null===d)&&(d=b.toLowerCase().replace(/ ?\n/g," ")),f="#"+d,e.helper.isUndefined(k[d]))return a;f=k[d],e.helper.isUndefined(l[d])||(j=l[d]),e.helper.isUndefined(m[d])||(g=m[d].width,h=m[d].height)}b=b.replace(/"/g,""").replace(e.helper.regexes.asteriskAndDash,e.helper.escapeCharactersCallback),f=f.replace(e.helper.regexes.asteriskAndDash,e.helper.escapeCharactersCallback);var n='
"}a=c.converter._dispatch("images.before",a,b,c);var f=/!\[(.*?)]\s?\([ \t]*()(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g,g=/!\[([^\]]*?)] ?(?:\n *)?\[(.*?)]()()()()()/g;return a=a.replace(g,d),a=a.replace(f,d),a=c.converter._dispatch("images.after",a,b,c)}),e.subParser("italicsAndBold",function(a,b,c){"use strict";return a=c.converter._dispatch("italicsAndBold.before",a,b,c),b.literalMidWordUnderscores?(a=a.replace(/(^|\s|>|\b)__(?=\S)([\s\S]+?)__(?=\b|<|\s|$)/gm,"$1$2"),a=a.replace(/(^|\s|>|\b)_(?=\S)([\s\S]+?)_(?=\b|<|\s|$)/gm,"$1$2"),a=a.replace(/(\*\*)(?=\S)([^\r]*?\S[*]*)\1/g,"$2"),a=a.replace(/(\*)(?=\S)([^\r]*?\S)\1/g,"$2")):(a=a.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,"$2"),a=a.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"$2")),a=c.converter._dispatch("italicsAndBold.after",a,b,c)}),e.subParser("lists",function(a,b,c){"use strict";function d(a,d){c.gListLevel++,a=a.replace(/\n{2,}$/,"\n"),a+="¨0";var f=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0| {0,3}([*+-]|\d+[.])[ \t]+))/gm,g=/\n[ \t]*\n(?!¨0)/.test(a);return b.disableForced4SpacesIndentedSublists&&(f=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0|\2([*+-]|\d+[.])[ \t]+))/gm),a=a.replace(f,function(a,d,f,h,i,j,k){k=k&&""!==k.trim();var l=e.subParser("outdent")(i,b,c),m="";return j&&b.tasklists&&(m=' class="task-list-item" style="list-style-type: none;"',l=l.replace(/^[ \t]*\[(x|X| )?]/m,function(){var a='"})),l=l.replace(/^([-*+]|\d\.)[ \t]+[\S\n ]*/g,function(a){return"¨A"+a}),d||l.search(/\n{2,}/)>-1?(l=e.subParser("githubCodeBlocks")(l,b,c),l=e.subParser("blockGamut")(l,b,c)):(l=e.subParser("lists")(l,b,c),l=l.replace(/\n$/,""),l=e.subParser("hashHTMLBlocks")(l,b,c),l=l.replace(/\n\n+/g,"\n\n"),l=l.replace(/\n\n/g,"¨B"),l=g?e.subParser("paragraphs")(l,b,c):e.subParser("spanGamut")(l,b,c),l=l.replace(/¨B/g,"\n\n")),l=l.replace("¨A",""),l=""+l+"\n"}),a=a.replace(/¨0/g,""),c.gListLevel--,d&&(a=a.replace(/\s+$/,"")),a}function f(a,c,e){var f=b.disableForced4SpacesIndentedSublists?/^ ?\d+\.[ \t]/gm:/^ {0,3}\d+\.[ \t]/gm,g=b.disableForced4SpacesIndentedSublists?/^ ?[*+-][ \t]/gm:/^ {0,3}[*+-][ \t]/gm,h="ul"===c?f:g,i="";return-1!==a.search(h)?!function j(a){var b=a.search(h);-1!==b?(i+="\n<"+c+">\n"+d(a.slice(0,b),!!e)+""+c+">\n",c="ul"===c?"ol":"ul",h="ul"===c?f:g,j(a.slice(b))):i+="\n<"+c+">\n"+d(a,!!e)+""+c+">\n"}(a):i="\n<"+c+">\n"+d(a,!!e)+""+c+">\n",i}return a=c.converter._dispatch("lists.before",a,b,c),a+="¨0",a=c.gListLevel?a.replace(/^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(a,b,c){var d=c.search(/[*+-]/g)>-1?"ul":"ol";return f(b,d,!0)}):a.replace(/(\n\n|^\n?)(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(a,b,c,d){var e=d.search(/[*+-]/g)>-1?"ul":"ol";return f(c,e,!1)}),a=a.replace(/¨0/,""),a=c.converter._dispatch("lists.after",a,b,c)}),e.subParser("outdent",function(a){"use strict";return a=a.replace(/^(\t|[ ]{1,4})/gm,"¨0"),a=a.replace(/¨0/g,"")}),e.subParser("paragraphs",function(a,b,c){"use strict";a=c.converter._dispatch("paragraphs.before",a,b,c),a=a.replace(/^\n+/g,""),a=a.replace(/\n+$/g,"");for(var d=a.split(/\n{2,}/g),f=[],g=d.length,h=0;g>h;h++){var i=d[h];i.search(/¨(K|G)(\d+)\1/g)>=0?f.push(i):(i=e.subParser("spanGamut")(i,b,c),i=i.replace(/^([ \t]*)/g,""),i+="
",f.push(i))}for(g=f.length,h=0;g>h;h++){for(var j="",k=f[h],l=!1;k.search(/¨(K|G)(\d+)\1/)>=0;){var m=RegExp.$1,n=RegExp.$2;j="K"===m?c.gHtmlBlocks[n]:l?e.subParser("encodeCode")(c.ghCodeBlocks[n].text):c.ghCodeBlocks[n].codeblock,j=j.replace(/\$/g,"$$$$"),k=k.replace(/(\n\n)?¨(K|G)\d+\2(\n\n)?/,j),/^]*>\s*]*>/.test(k)&&(l=!0)}f[h]=k}return a=f.join("\n"),a=a.replace(/^\n+/g,""),a=a.replace(/\n+$/g,""),c.converter._dispatch("paragraphs.after",a,b,c)}),e.subParser("runExtension",function(a,b,c,d){"use strict";if(a.filter)b=a.filter(b,d.converter,c);else if(a.regex){var e=a.regex;!e instanceof RegExp&&(e=new RegExp(e,"g")),b=b.replace(e,a.replace)}return b}),e.subParser("spanGamut",function(a,b,c){"use strict";return a=c.converter._dispatch("spanGamut.before",a,b,c),a=e.subParser("codeSpans")(a,b,c),a=e.subParser("escapeSpecialCharsWithinTagAttributes")(a,b,c),a=e.subParser("encodeBackslashEscapes")(a,b,c),a=e.subParser("images")(a,b,c),a=e.subParser("anchors")(a,b,c),a=e.subParser("autoLinks")(a,b,c),a=e.subParser("encodeAmpsAndAngles")(a,b,c),a=e.subParser("italicsAndBold")(a,b,c),a=e.subParser("strikethrough")(a,b,c),a=b.simpleLineBreaks?a.replace(/\n/g,"
\n"):a.replace(/ +\n/g,"
\n"),a=c.converter._dispatch("spanGamut.after",a,b,c)}),e.subParser("strikethrough",function(a,b,c){"use strict";return b.strikethrough&&(a=c.converter._dispatch("strikethrough.before",a,b,c),a=a.replace(/(?:~){2}([\s\S]+?)(?:~){2}/g,"$1"),a=c.converter._dispatch("strikethrough.after",a,b,c)),a}),e.subParser("stripBlankLines",function(a){"use strict";return a.replace(/^[ \t]+$/gm,"")}),e.subParser("stripLinkDefinitions",function(a,b,c){"use strict";var d=/^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=¨0))/gm;return a+="¨0",a=a.replace(d,function(a,d,f,g,h,i,j){return d=d.toLowerCase(),c.gUrls[d]=e.subParser("encodeAmpsAndAngles")(f),i?i+j:(j&&(c.gTitles[d]=j.replace(/"|'/g,""")),b.parseImgDimensions&&g&&h&&(c.gDimensions[d]={width:g,height:h}),"")}),a=a.replace(/¨0/,"")}),e.subParser("tables",function(a,b,c){"use strict";function d(a){return/^:[ \t]*--*$/.test(a)?' style="text-align:left;"':/^--*[ \t]*:[ \t]*$/.test(a)?' style="text-align:right;"':/^:[ \t]*--*[ \t]*:$/.test(a)?' style="text-align:center;"':""}function f(a,d){var f="";return a=a.trim(),b.tableHeaderId&&(f=' id="'+a.replace(/ /g,"_").toLowerCase()+'"'),a=e.subParser("spanGamut")(a,b,c),""+a+" | \n"}function g(a,d){var f=e.subParser("spanGamut")(a,b,c);return""+f+" | \n"}function h(a,b){for(var c="\n\n\n",d=a.length,e=0;d>e;++e)c+=a[e];for(c+="
\n\n\n",e=0;e\n";for(var f=0;d>f;++f)c+=b[e][f];c+="\n"}return c+="\n
\n"}if(!b.tables)return a;var i=/^ {0,3}\|?.+\|.+\n[ \t]{0,3}\|?[ \t]*:?[ \t]*(?:-|=){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:-|=){2,}[\s\S]+?(?:\n\n|¨0)/gm;return a=c.converter._dispatch("tables.before",a,b,c),a=a.replace(i,function(a){var b,c=a.split("\n");for(b=0;b';
+
+ var buttonTemplate = '' + '';
+
+ return {
+ link: function link($scope, elem) {
+ var categories = _dfunc2.default.getCategories();
+ var allFunctions = getAllFunctionNames(categories);
+ var ctrl = $scope.ctrl;
+
+ $scope.functionMenu = createFunctionDropDownMenu(categories);
+
+ var $input = (0, _jquery2.default)(inputTemplate);
+ var $button = (0, _jquery2.default)(buttonTemplate);
+ $input.appendTo(elem);
+ $button.appendTo(elem);
+
+ $input.attr('data-provide', 'typeahead');
+ $input.typeahead({
+ source: allFunctions,
+ minLength: 1,
+ items: 10,
+ updater: function updater(value) {
+ var funcDef = _dfunc2.default.getFuncDef(value);
+ if (!funcDef) {
+ // try find close match
+ value = value.toLowerCase();
+ funcDef = _lodash2.default.find(allFunctions, function (funcName) {
+ return funcName.toLowerCase().indexOf(value) === 0;
+ });
+
+ if (!funcDef) {
+ return;
+ }
+ }
+
+ $scope.$apply(function () {
+ ctrl.addFunction(funcDef);
+ });
+
+ $input.trigger('blur');
+ return '';
+ }
+ });
+
+ $button.click(function () {
+ $button.hide();
+ $input.show();
+ $input.focus();
+ });
+
+ $input.keyup(function () {
+ elem.toggleClass('open', $input.val() === '');
+ });
+
+ $input.blur(function () {
+ // clicking the function dropdown menu wont
+ // work if you remove class at once
+ setTimeout(function () {
+ $input.val('');
+ $input.hide();
+ $button.show();
+ elem.removeClass('open');
+ }, 200);
+ });
+
+ $compile(elem.contents())($scope);
+ }
+ };
+});
+
+function getAllFunctionNames(categories) {
+ return _lodash2.default.reduce(categories, function (list, category) {
+ _lodash2.default.each(category, function (func) {
+ list.push(func.name);
+ });
+ return list;
+ }, []);
+}
+
+function createFunctionDropDownMenu(categories) {
+ return _lodash2.default.map(categories, function (list, category) {
+ return {
+ text: category,
+ submenu: _lodash2.default.map(list, function (value) {
+ return {
+ text: value.name,
+ click: "ctrl.addFunction('" + value.name + "')"
+ };
+ })
+ };
+ });
+}
+//# sourceMappingURL=add_datadog_func.js.map
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/add_datadog_func.js.map b/loadTest/docker/grafana/plugins/datadog/dist/test/add_datadog_func.js.map
new file mode 100644
index 0000000..304c5e5
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/add_datadog_func.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/add_datadog_func.js"],"names":["module","directive","$compile","inputTemplate","buttonTemplate","link","$scope","elem","categories","getCategories","allFunctions","getAllFunctionNames","ctrl","functionMenu","createFunctionDropDownMenu","$input","$button","appendTo","attr","typeahead","source","minLength","items","updater","value","funcDef","getFuncDef","toLowerCase","find","funcName","indexOf","$apply","addFunction","trigger","click","hide","show","focus","keyup","toggleClass","val","blur","setTimeout","removeClass","contents","reduce","list","category","each","func","push","name","map","text","submenu"],"mappings":"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","file":"add_datadog_func.js","sourcesContent":["import angular from 'angular';\nimport _ from 'lodash';\nimport $ from 'jquery';\nimport dfunc from './dfunc';\n\nangular\n .module('grafana.directives')\n .directive('datadogAddFunc', function($compile) {\n var inputTemplate = '';\n\n var buttonTemplate = '' +\n '';\n\n return {\n link: function($scope, elem) {\n var categories = dfunc.getCategories();\n var allFunctions = getAllFunctionNames(categories);\n var ctrl = $scope.ctrl;\n\n $scope.functionMenu = createFunctionDropDownMenu(categories);\n\n var $input = $(inputTemplate);\n var $button = $(buttonTemplate);\n $input.appendTo(elem);\n $button.appendTo(elem);\n\n $input.attr('data-provide', 'typeahead');\n $input.typeahead({\n source: allFunctions,\n minLength: 1,\n items: 10,\n updater: function (value) {\n var funcDef = dfunc.getFuncDef(value);\n if (!funcDef) {\n // try find close match\n value = value.toLowerCase();\n funcDef = _.find(allFunctions, function(funcName) {\n return funcName.toLowerCase().indexOf(value) === 0;\n });\n\n if (!funcDef) { return; }\n }\n\n $scope.$apply(function() {\n ctrl.addFunction(funcDef);\n });\n\n $input.trigger('blur');\n return '';\n }\n });\n\n $button.click(function() {\n $button.hide();\n $input.show();\n $input.focus();\n });\n\n $input.keyup(function() {\n elem.toggleClass('open', $input.val() === '');\n });\n\n $input.blur(function() {\n // clicking the function dropdown menu wont\n // work if you remove class at once\n setTimeout(function() {\n $input.val('');\n $input.hide();\n $button.show();\n elem.removeClass('open');\n }, 200);\n });\n\n $compile(elem.contents())($scope);\n }\n };\n });\n\nfunction getAllFunctionNames(categories) {\n return _.reduce(categories, function(list, category) {\n _.each(category, function(func) {\n list.push(func.name);\n });\n return list;\n }, []);\n}\n\nfunction createFunctionDropDownMenu(categories) {\n return _.map(categories, function(list, category) {\n return {\n text: category,\n submenu: _.map(list, function(value) {\n return {\n text: value.name,\n click: \"ctrl.addFunction('\" + value.name + \"')\",\n };\n })\n };\n });\n}\n"]}
\ No newline at end of file
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/datasource.js b/loadTest/docker/grafana/plugins/datadog/dist/test/datasource.js
new file mode 100644
index 0000000..5765dc1
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/datasource.js
@@ -0,0 +1,422 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.DataDogDatasource = undefined;
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _lodash = require('lodash');
+
+var _lodash2 = _interopRequireDefault(_lodash);
+
+var _showdownMin = require('./showdown.min.js');
+
+var _showdownMin2 = _interopRequireDefault(_showdownMin);
+
+var _query_builder = require('./query_builder');
+
+var queryBuilder = _interopRequireWildcard(_query_builder);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var DataDogDatasource = exports.DataDogDatasource = function () {
+ function DataDogDatasource(instanceSettings, backendSrv, templateSrv) {
+ _classCallCheck(this, DataDogDatasource);
+
+ this.type = instanceSettings.type;
+ this.url = instanceSettings.url;
+ this.name = instanceSettings.name;
+ this.api_key = instanceSettings.jsonData.api_key;
+ this.application_key = instanceSettings.jsonData.app_key;
+ this.supportMetrics = true;
+ this.backendSrv = backendSrv;
+ this.templateSrv = templateSrv;
+ this._cached_metrics = false;
+ }
+
+ // Function to check Datasource health
+
+
+ _createClass(DataDogDatasource, [{
+ key: 'testDatasource',
+ value: function testDatasource() {
+ return this.invokeDataDogApiRequest('/downtime').then(function () {
+ return {
+ status: "success",
+ title: "Success",
+ message: "Data source is working"
+ };
+ }).catch(function (error) {
+ var message = "Connection error";
+ if (error && error.message) {
+ message = error.message;
+ }
+
+ return {
+ status: "error",
+ title: "Error",
+ message: message
+ };
+ });
+ }
+ }, {
+ key: 'tagFindQuery',
+ value: function tagFindQuery() {
+ return this.getTagsFromCache().then(function (tags) {
+ return _lodash2.default.map(tags, function (hosts, tag) {
+ return {
+ text: tag,
+ value: tag
+ };
+ });
+ });
+ }
+ }, {
+ key: 'metricFindQuery',
+ value: function metricFindQuery(query) {
+ var _this = this;
+
+ if (query === 'tag') {
+ return this.tagFindQuery();
+ }
+
+ if (this._cached_metrics) {
+ return Promise.resolve(this._cached_metrics);
+ }
+
+ if (this.fetching) {
+ return this.fetching;
+ }
+
+ var d = new Date();
+ d.setDate(d.getDate() - 1);
+ var from = Math.floor(d.getTime() / 1000);
+
+ this.fetching = this.getMetrics(from).then(function (metrics) {
+ _this._cached_metrics = _lodash2.default.map(metrics, function (metric) {
+ return {
+ text: metric,
+ value: metric
+ };
+ });
+
+ return _this._cached_metrics;
+ });
+
+ return this.fetching;
+ }
+ }, {
+ key: 'getTagKeys',
+ value: function getTagKeys() {
+ return this.getTagsFromCache().then(function (tagsHosts) {
+ var tags = Object.keys(tagsHosts);
+ var kv = mapTagsToKVPairs(tags);
+ var grafanaTags = Object.keys(kv);
+
+ return grafanaTags.map(function (tag) {
+ return {
+ text: tag,
+ value: tag
+ };
+ });
+ });
+ }
+ }, {
+ key: 'getTagValues',
+ value: function getTagValues(options) {
+ return this.getTagsFromCache().then(function (tagsHosts) {
+ var tags = Object.keys(tagsHosts);
+ var kv = mapTagsToKVPairs(tags);
+ var grafanaValues = kv[options.key];
+ return grafanaValues.map(function (val) {
+ return {
+ text: val,
+ value: val
+ };
+ });
+ });
+ }
+ }, {
+ key: 'query',
+ value: function query(options) {
+ var from = Math.floor(options.range.from.valueOf() / 1000);
+ var to = Math.floor(options.range.to.valueOf() / 1000);
+
+ var targets = options.targets.filter(function (t) {
+ return !t.hide;
+ });
+
+ if (targets.length <= 0) {
+ return Promise.resolve({ data: [] });
+ }
+
+ // add global adhoc filters
+ var adhocFilters = this.templateSrv.getAdhocFilters(this.name);
+
+ var queries = _lodash2.default.map(targets, function (target) {
+ var query = void 0;
+ if (target.rawQuery) {
+ query = target.query;
+ } else {
+ query = queryBuilder.buildQuery(target, adhocFilters);
+ }
+ return query;
+ });
+
+ var queryString = queries.join(',');
+ queryString = this.templateSrv.replace(queryString, options.scopedVars, dataDogTagFormat);
+
+ var params = {
+ from: from,
+ to: to,
+ query: queryString
+ };
+
+ return this.invokeDataDogApiRequest('/query', params).then(function (result) {
+ var dataResponse = _lodash2.default.map(result.series, function (series, i) {
+ var target = targets[i];
+ return {
+ 'target': target.alias || series.expression,
+ 'datapoints': _lodash2.default.map(series.pointlist, function (point) {
+ return [point[1], point[0]];
+ })
+ };
+ });
+
+ return { data: dataResponse };
+ });
+ }
+ }, {
+ key: 'annotationQuery',
+ value: function annotationQuery(options) {
+ var timeFrom = Math.floor(options.range.from.valueOf() / 1000);
+ var timeTo = Math.floor(options.range.to.valueOf() / 1000);
+ var _options$annotation = options.annotation,
+ priority = _options$annotation.priority,
+ sources = _options$annotation.sources,
+ tags = _options$annotation.tags;
+
+
+ return this.getEventStream(timeFrom, timeTo, priority, sources, tags).then(function (eventStreams) {
+ var eventAnnotations = eventStreams.map(function (eventStream) {
+ var allEvents = eventStream.children;
+ var filteredEvents = _lodash2.default.filter(allEvents, function (event) {
+ return event.alert_type !== 'success';
+ });
+
+ return _lodash2.default.map(filteredEvents, function (event) {
+ var renderedText = eventStream.text;
+ if (isDataDogMarkdown(eventStream.text)) {
+ renderedText = convertDataDogMdToHtml(eventStream.text);
+ }
+
+ return {
+ annotation: options.annotation,
+ time: event.date_happened * 1000,
+ title: eventStream.title,
+ text: renderedText,
+ tags: eventStream.tags
+ };
+ });
+ });
+
+ return _lodash2.default.flatten(eventAnnotations);
+ });
+ }
+ }, {
+ key: 'getHosts',
+ value: function getHosts() {
+ return this.searchEntities('hosts');
+ }
+
+ // entity should be 'hosts' or 'metrics'
+ // http://docs.datadoghq.com/api/?lang=console#search
+
+ }, {
+ key: 'searchEntities',
+ value: function searchEntities(entity) {
+ var params = { q: '' };
+ if (entity) {
+ params.q = entity + ':';
+ }
+
+ return this.invokeDataDogApiRequest('/search', params).then(function (result) {
+ if (result && result.results) {
+ return result.results[entity];
+ }
+ });
+ }
+ }, {
+ key: 'getMetrics',
+ value: function getMetrics(timeFrom) {
+ var params = {};
+
+ if (timeFrom) {
+ params.from = timeFrom;
+ }
+
+ return this.invokeDataDogApiRequest('/metrics', params).then(function (result) {
+ if (result.metrics) {
+ return result.metrics;
+ } else {
+ return [];
+ }
+ });
+ }
+ }, {
+ key: 'getTagsHosts',
+ value: function getTagsHosts() {
+ return this.invokeDataDogApiRequest('/tags/hosts').then(function (result) {
+ if (result && result.tags) {
+ return result.tags;
+ }
+ });
+ }
+ }, {
+ key: 'getTagsFromCache',
+ value: function getTagsFromCache() {
+ var _this2 = this;
+
+ var getTags = void 0;
+ if (this._cached_tags && this._cached_tags.length) {
+ getTags = Promise.resolve(this._cached_tags);
+ } else {
+ getTags = this.getTagsHosts().then(function (tags) {
+ _this2._cached_tags = tags;
+ return tags;
+ });
+ }
+
+ return getTags;
+ }
+ }, {
+ key: 'getEventStream',
+ value: function getEventStream(timeFrom, timeTo, priority, sources, tags) {
+ var params = {
+ start: timeFrom,
+ end: timeTo
+ };
+ if (priority) {
+ params.priority = priority;
+ }
+ if (sources) {
+ params.sources = sources;
+ }
+ if (tags) {
+ params.tags = tags;
+ }
+
+ return this.invokeDataDogApiRequest('/events', params).then(function (result) {
+ if (result.events) {
+ return result.events;
+ } else {
+ return [];
+ }
+ });
+ }
+ }, {
+ key: 'invokeDataDogApiRequest',
+ value: function invokeDataDogApiRequest(url) {
+ var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ // Set auth params
+ params.api_key = this.api_key;
+ params.application_key = this.application_key;
+
+ return this.backendSrv.datasourceRequest({
+ method: 'GET',
+ url: this.url + url,
+ params: params
+ }).then(function (response) {
+ if (response.data) {
+ return response.data;
+ } else {
+ throw { message: 'DataDog API request error' };
+ }
+ }).catch(function (error) {
+ var message = 'DataDog API request error';
+ if (error.statusText) {
+ message = error.status + ' ' + error.statusText;
+ throw { message: message };
+ } else if (error.err.statusText) {
+ throw { message: error.err.statusText };
+ } else {
+ throw { message: message };
+ }
+ });
+ }
+ }]);
+
+ return DataDogDatasource;
+}();
+
+/*
+ * Convert tags to key-value pairs
+ * [region:east, region:nw] => {region: [east, nw]}
+ */
+
+
+function mapTagsToKVPairs(tags) {
+ var kv_tags = _lodash2.default.filter(tags, function (tag) {
+ return tag.indexOf(':') !== -1;
+ });
+
+ var kv_pairs = kv_tags.map(function (tag) {
+ return tag.split(':', 2); // Limit to 2
+ });
+
+ var kv_object = {};
+ kv_pairs.forEach(function (pair) {
+ var key = pair[0];
+ var val = pair[1];
+
+ if (kv_object[key]) {
+ kv_object[key].push(val);
+ } else {
+ kv_object[key] = [val];
+ }
+ });
+
+ return kv_object;
+}
+
+/*
+ * Convert DataDog event text from markdown to pure HTML
+ * http://docs.datadoghq.com/guides/markdown/
+ */
+function convertDataDogMdToHtml(str) {
+ var MD_START = '%%%\n';
+ var MD_END = '\n%%%';
+
+ var md_start_index = str.indexOf(MD_START) + MD_START.length;
+ var md_end_index = str.indexOf(MD_END);
+ var md = str.substring(md_start_index, md_end_index);
+ md = removeImagesFromDataDogMarkdown(md);
+
+ var converter = new _showdownMin2.default.Converter();
+ return converter.makeHtml(md);
+}
+
+function isDataDogMarkdown(str) {
+ var MD_START = '%%%\n';
+ return str.indexOf(MD_START) >= 0;
+}
+
+function removeImagesFromDataDogMarkdown(str) {
+ var dataDogImagePattern = /\[{0,1}\!\[.*\]\(https{0,1}\:\/\/p\.datadoghq\.com\/snapshot.+\)/g;
+ return str.replace(dataDogImagePattern, '');
+}
+
+function dataDogTagFormat(value) {
+ if (_lodash2.default.isArray(value)) {
+ return value.join(',');
+ }
+ return value;
+}
+//# sourceMappingURL=datasource.js.map
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/datasource.js.map b/loadTest/docker/grafana/plugins/datadog/dist/test/datasource.js.map
new file mode 100644
index 0000000..27172e5
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/datasource.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/datasource.js"],"names":["queryBuilder","DataDogDatasource","instanceSettings","backendSrv","templateSrv","type","url","name","api_key","jsonData","application_key","app_key","supportMetrics","_cached_metrics","invokeDataDogApiRequest","then","status","title","message","catch","error","getTagsFromCache","map","tags","hosts","tag","text","value","query","tagFindQuery","Promise","resolve","fetching","d","Date","setDate","getDate","from","Math","floor","getTime","getMetrics","metrics","metric","Object","keys","tagsHosts","kv","mapTagsToKVPairs","grafanaTags","options","grafanaValues","key","val","range","valueOf","to","targets","filter","t","hide","length","data","adhocFilters","getAdhocFilters","queries","target","rawQuery","buildQuery","queryString","join","replace","scopedVars","dataDogTagFormat","params","dataResponse","result","series","i","alias","expression","pointlist","point","timeFrom","timeTo","annotation","priority","sources","getEventStream","eventAnnotations","eventStreams","allEvents","eventStream","children","filteredEvents","event","alert_type","renderedText","isDataDogMarkdown","convertDataDogMdToHtml","time","date_happened","flatten","searchEntities","entity","q","results","getTags","_cached_tags","getTagsHosts","start","end","events","datasourceRequest","method","response","statusText","err","kv_tags","indexOf","kv_pairs","split","kv_object","forEach","pair","push","str","MD_START","MD_END","md_start_index","md_end_index","md","substring","removeImagesFromDataDogMarkdown","converter","Converter","makeHtml","dataDogImagePattern","isArray"],"mappings":"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","file":"datasource.js","sourcesContent":["import _ from 'lodash';\nimport showdown from './showdown.min.js';\nimport * as queryBuilder from './query_builder';\n\nexport class DataDogDatasource {\n\n constructor (instanceSettings, backendSrv, templateSrv) {\n this.type = instanceSettings.type;\n this.url = instanceSettings.url;\n this.name = instanceSettings.name;\n this.api_key = instanceSettings.jsonData.api_key;\n this.application_key = instanceSettings.jsonData.app_key;\n this.supportMetrics = true;\n this.backendSrv = backendSrv;\n this.templateSrv = templateSrv;\n this._cached_metrics = false;\n }\n\n // Function to check Datasource health\n testDatasource() {\n return this.invokeDataDogApiRequest('/downtime')\n .then(() => {\n return {\n status: \"success\",\n title: \"Success\",\n message: \"Data source is working\"\n };\n })\n .catch(error => {\n var message = \"Connection error\";\n if (error && error.message) {\n message = error.message;\n }\n\n return {\n status: \"error\",\n title: \"Error\",\n message: message\n };\n });\n }\n\n tagFindQuery() {\n return this.getTagsFromCache()\n .then(tags => {\n return _.map(tags, (hosts, tag) => {\n return {\n text: tag,\n value: tag,\n };\n });\n });\n }\n\n metricFindQuery(query) {\n if (query === 'tag') {\n return this.tagFindQuery();\n }\n\n if (this._cached_metrics) {\n return Promise.resolve(this._cached_metrics);\n }\n\n if (this.fetching) {\n return this.fetching;\n }\n\n var d = new Date();\n d.setDate(d.getDate() - 1);\n var from = Math.floor(d.getTime() / 1000);\n\n this.fetching = this.getMetrics(from).then(metrics => {\n this._cached_metrics = _.map(metrics, metric => {\n return {\n text: metric,\n value: metric,\n };\n });\n\n return this._cached_metrics;\n });\n\n return this.fetching;\n }\n\n getTagKeys() {\n return this.getTagsFromCache()\n .then(tagsHosts => {\n let tags = Object.keys(tagsHosts);\n let kv = mapTagsToKVPairs(tags);\n let grafanaTags = Object.keys(kv);\n\n return grafanaTags.map(tag => {\n return {\n text: tag,\n value: tag,\n };\n });\n });\n }\n\n getTagValues(options) {\n return this.getTagsFromCache()\n .then(tagsHosts => {\n let tags = Object.keys(tagsHosts);\n let kv = mapTagsToKVPairs(tags);\n let grafanaValues = kv[options.key];\n return grafanaValues.map(val => {\n return {\n text: val,\n value: val,\n };\n });\n });\n }\n\n query(options) {\n var from = Math.floor(options.range.from.valueOf() / 1000);\n var to = Math.floor(options.range.to.valueOf() / 1000);\n\n var targets = options.targets.filter(function (t) { return !t.hide; });\n\n if (targets.length <= 0) {\n return Promise.resolve({data: []});\n }\n\n // add global adhoc filters\n let adhocFilters = this.templateSrv.getAdhocFilters(this.name);\n\n var queries = _.map(targets, target => {\n let query;\n if (target.rawQuery) {\n query = target.query;\n } else {\n query = queryBuilder.buildQuery(target, adhocFilters);\n }\n return query;\n });\n\n var queryString = queries.join(',');\n queryString = this.templateSrv.replace(queryString, options.scopedVars, dataDogTagFormat);\n\n var params = {\n from: from,\n to: to,\n query: queryString,\n };\n\n return this.invokeDataDogApiRequest('/query', params)\n .then(result => {\n var dataResponse = _.map(result.series, (series, i) => {\n var target = targets[i];\n return {\n 'target': target.alias || series.expression,\n 'datapoints': _.map(series.pointlist, point => {\n return [point[1], point[0]];\n })\n };\n });\n\n return {data: dataResponse};\n });\n }\n\n annotationQuery(options) {\n let timeFrom = Math.floor(options.range.from.valueOf() / 1000);\n let timeTo = Math.floor(options.range.to.valueOf() / 1000);\n let {priority, sources, tags} = options.annotation;\n\n return this.getEventStream(timeFrom, timeTo, priority, sources, tags)\n .then(eventStreams => {\n let eventAnnotations = eventStreams.map(eventStream => {\n let allEvents = eventStream.children;\n let filteredEvents = _.filter(allEvents, event => {\n return event.alert_type !== 'success';\n });\n\n return _.map(filteredEvents, event => {\n let renderedText = eventStream.text;\n if (isDataDogMarkdown(eventStream.text)) {\n renderedText = convertDataDogMdToHtml(eventStream.text);\n }\n\n return {\n annotation: options.annotation,\n time: event.date_happened * 1000,\n title: eventStream.title,\n text: renderedText,\n tags: eventStream.tags\n };\n });\n });\n\n return _.flatten(eventAnnotations);\n });\n }\n\n getHosts() {\n return this.searchEntities('hosts');\n }\n\n // entity should be 'hosts' or 'metrics'\n // http://docs.datadoghq.com/api/?lang=console#search\n searchEntities(entity) {\n let params = {q: ''};\n if (entity) {\n params.q = `${entity}:`;\n }\n\n return this.invokeDataDogApiRequest('/search', params)\n .then(result => {\n if (result && result.results) {\n return result.results[entity];\n }\n });\n }\n\n getMetrics(timeFrom) {\n let params = {};\n\n if (timeFrom) {\n params.from = timeFrom;\n }\n\n return this.invokeDataDogApiRequest('/metrics', params)\n .then(result => {\n if (result.metrics) {\n return result.metrics;\n } else {\n return [];\n }\n });\n }\n\n getTagsHosts() {\n return this.invokeDataDogApiRequest('/tags/hosts')\n .then(result => {\n if (result && result.tags) {\n return result.tags;\n }\n });\n }\n\n getTagsFromCache() {\n let getTags;\n if (this._cached_tags && this._cached_tags.length) {\n getTags = Promise.resolve(this._cached_tags);\n } else {\n getTags = this.getTagsHosts().then(tags => {\n this._cached_tags = tags;\n return tags;\n });\n }\n\n return getTags;\n }\n\n getEventStream(timeFrom, timeTo, priority, sources, tags) {\n let params = {\n start: timeFrom,\n end: timeTo\n };\n if (priority) {\n params.priority = priority;\n }\n if (sources) {\n params.sources = sources;\n }\n if (tags) {\n params.tags = tags;\n }\n\n return this.invokeDataDogApiRequest('/events', params)\n .then(result => {\n if (result.events) {\n return result.events;\n } else {\n return [];\n }\n });\n }\n\n invokeDataDogApiRequest(url, params = {}) {\n // Set auth params\n params.api_key = this.api_key;\n params.application_key = this.application_key;\n\n return this.backendSrv.datasourceRequest({\n method: 'GET',\n url: this.url + url,\n params: params\n })\n .then(response => {\n if (response.data) {\n return response.data;\n } else {\n throw {message: 'DataDog API request error'};\n }\n })\n .catch(error => {\n var message = 'DataDog API request error';\n if (error.statusText) {\n message = error.status + ' ' + error.statusText;\n throw {message: message};\n } else if (error.err.statusText) {\n throw {message: error.err.statusText};\n } else {\n throw {message: message};\n }\n });\n }\n}\n\n/*\n * Convert tags to key-value pairs\n * [region:east, region:nw] => {region: [east, nw]}\n */\nfunction mapTagsToKVPairs(tags) {\n let kv_tags = _.filter(tags, tag => {\n return (tag.indexOf(':') !== -1);\n });\n\n let kv_pairs = kv_tags.map(tag => {\n return tag.split(':', 2); // Limit to 2\n });\n\n let kv_object = {};\n kv_pairs.forEach(pair => {\n let key = pair[0];\n let val = pair[1];\n\n if (kv_object[key]) {\n kv_object[key].push(val);\n } else {\n kv_object[key] = [val];\n }\n });\n\n return kv_object;\n}\n\n/*\n * Convert DataDog event text from markdown to pure HTML\n * http://docs.datadoghq.com/guides/markdown/\n */\nfunction convertDataDogMdToHtml(str) {\n const MD_START = '%%%\\n';\n const MD_END = '\\n%%%';\n\n let md_start_index = str.indexOf(MD_START) + MD_START.length;\n let md_end_index = str.indexOf(MD_END);\n let md = str.substring(md_start_index, md_end_index);\n md = removeImagesFromDataDogMarkdown(md);\n\n let converter = new showdown.Converter();\n return converter.makeHtml(md);\n}\n\nfunction isDataDogMarkdown(str) {\n const MD_START = '%%%\\n';\n return str.indexOf(MD_START) >= 0;\n}\n\nfunction removeImagesFromDataDogMarkdown(str) {\n let dataDogImagePattern = /\\[{0,1}\\!\\[.*\\]\\(https{0,1}\\:\\/\\/p\\.datadoghq\\.com\\/snapshot.+\\)/g;\n return str.replace(dataDogImagePattern, '');\n}\n\nfunction dataDogTagFormat(value) {\n if (_.isArray(value)) {\n return value.join(',');\n }\n return value;\n}\n"]}
\ No newline at end of file
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/dfunc.js b/loadTest/docker/grafana/plugins/datadog/dist/test/dfunc.js
new file mode 100644
index 0000000..f8b87b1
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/dfunc.js
@@ -0,0 +1,295 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _lodash = require('lodash');
+
+var _lodash2 = _interopRequireDefault(_lodash);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var index = [];
+var categories = {
+ Arithmatic: [],
+ Interpolation: [],
+ Timeshift: [],
+ Rate: [],
+ Smoothing: [],
+ Rollup: [],
+ Rank: [],
+ Count: [],
+ Regression: [],
+ Algorithms: []
+};
+
+function addFuncDef(funcDef) {
+ funcDef.params = funcDef.params || [];
+ funcDef.defaultParams = funcDef.defaultParams || [];
+ funcDef.append = funcDef.append || false;
+ if (funcDef.category) {
+ funcDef.category.push(funcDef);
+ }
+ index[funcDef.name] = funcDef;
+ index[funcDef.name] = funcDef;
+}
+
+addFuncDef({
+ name: 'abs',
+ category: categories.Arithmatic
+});
+
+addFuncDef({
+ name: 'log2',
+ category: categories.Arithmatic
+});
+
+addFuncDef({
+ name: 'cumsum',
+ category: categories.Arithmatic
+});
+
+addFuncDef({
+ name: 'integral',
+ category: categories.Arithmatic
+});
+
+addFuncDef({
+ name: 'fill',
+ category: categories.Interpolation,
+ params: [{ name: "fill", type: 'string', options: ['null', 'zero', 'linear', 'last'] }, { name: "limit", type: 'int', optional: true }],
+ defaultParams: ['zero'],
+ append: true
+});
+
+addFuncDef({
+ name: 'hour_before',
+ category: categories.Timeshift
+});
+
+addFuncDef({
+ name: 'day_before',
+ category: categories.Timeshift
+});
+
+addFuncDef({
+ name: 'week_before',
+ category: categories.Timeshift
+});
+
+addFuncDef({
+ name: 'month_before',
+ category: categories.Timeshift
+});
+
+addFuncDef({
+ name: 'per_second',
+ category: categories.Rate
+});
+
+addFuncDef({
+ name: 'per_minute',
+ category: categories.Rate
+});
+
+addFuncDef({
+ name: 'per_hour',
+ category: categories.Rate
+});
+
+addFuncDef({
+ name: 'dt',
+ category: categories.Rate
+});
+
+addFuncDef({
+ name: 'diff',
+ category: categories.Rate
+});
+
+addFuncDef({
+ name: 'derivative',
+ category: categories.Rate
+});
+
+addFuncDef({
+ name: 'ewma_3',
+ category: categories.Smoothing
+});
+
+addFuncDef({
+ name: 'ewma_5',
+ category: categories.Smoothing
+});
+
+addFuncDef({
+ name: 'ewma_10',
+ category: categories.Smoothing
+});
+
+addFuncDef({
+ name: 'ewma_20',
+ category: categories.Smoothing
+});
+
+addFuncDef({
+ name: 'median_3',
+ category: categories.Smoothing
+});
+
+addFuncDef({
+ name: 'median_5',
+ category: categories.Smoothing
+});
+
+addFuncDef({
+ name: 'median_7',
+ category: categories.Smoothing
+});
+
+addFuncDef({
+ name: 'median_9',
+ category: categories.Smoothing
+});
+
+addFuncDef({
+ name: 'rollup',
+ category: categories.Rollup,
+ params: [{ name: "rollup", type: 'string', options: ['sum', 'avg', 'min', 'max'] }, { name: "period", type: 'int' }],
+ defaultParams: ['sum', 600],
+ append: true
+});
+
+addFuncDef({
+ name: 'count_nonzero',
+ category: categories.Count
+});
+
+addFuncDef({
+ name: 'count_not_null',
+ category: categories.Count
+});
+
+addFuncDef({
+ name: 'top',
+ category: categories.Rank,
+ params: [{ name: "top", type: 'int', options: [5, 10, 15] }, { name: "aggr", type: 'string', options: ['mean', 'min', 'max', 'area', 'l2norm', 'last'] }, { name: "dir", type: 'string', options: ['asc', 'desc'] }],
+ defaultParams: [5, 'mean', 'dir']
+});
+
+addFuncDef({
+ name: 'robust_trend',
+ category: categories.Regression
+});
+
+addFuncDef({
+ name: 'piecewise_constant',
+ category: categories.Regression
+});
+
+addFuncDef({
+ name: 'anomalies',
+ category: categories.Algorithms,
+ params: [{ name: "anomalies", type: 'string', options: ['basic', 'agile', 'robust', 'adaptive'] }, { name: "bonds", type: 'int', options: [1, 2, 3, 4, 5, 6] }],
+ defaultParams: ['basic', 2]
+});
+
+addFuncDef({
+ name: 'anomalies',
+ category: categories.Algorithms,
+ params: [{ name: "anomalies", type: 'string', options: ['basic', 'agile', 'robust', 'adaptive'] }, { name: "bonds", type: 'int', options: [1, 2, 3, 4, 5, 6] }],
+ defaultParams: ['basic', 2]
+});
+
+addFuncDef({
+ name: 'outliers',
+ category: categories.Algorithms,
+ params: [{ name: "outliers", type: 'string', options: ['DBSCAN', 'MAD', 'scaledDBSCAN', 'scaledMAD'] }, { name: "tolerance", type: 'int', options: [0.33, 0.5, 1, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] }],
+ defaultParams: ['basic', 2]
+});
+
+_lodash2.default.each(categories, function (funcList, catName) {
+ categories[catName] = _lodash2.default.sortBy(funcList, 'name');
+});
+
+function FuncInstance(funcDef, options) {
+ this.def = funcDef;
+ this.params = [];
+
+ if (options && options.withDefaultParams) {
+ this.params = funcDef.defaultParams.slice(0);
+ }
+
+ this.updateText();
+}
+
+FuncInstance.prototype.render = function (metricExp) {
+ var str = this.def.name + '(';
+ var parameters = _lodash2.default.map(this.params, function (value) {
+ return value;
+ });
+ if (metricExp && !this.def.append) {
+ parameters.unshift(metricExp);
+ }
+
+ var funcRender = str + parameters.join(', ') + ')';
+ console.log(funcRender, this.def.append);
+ if (this.def.append) {
+ return metricExp + '.' + funcRender;
+ } else {
+ return funcRender;
+ }
+};
+
+FuncInstance.prototype.updateParam = function (strValue, index) {
+ // handle optional parameters
+ // if string contains ',' and next param is optional, split and update both
+
+ if (strValue === '' && this.def.params[index].optional) {
+ this.params.splice(index, 1);
+ } else {
+ this.params[index] = strValue;
+ }
+
+ this.updateText();
+};
+
+FuncInstance.prototype.updateText = function () {
+ if (this.params.length === 0) {
+ this.text = this.def.name + '()';
+ return;
+ }
+
+ var text = this.def.name + '(';
+ text += this.params.join(', ');
+ text += ')';
+ this.text = text;
+};
+
+var createFuncInstance = function createFuncInstance(funcDef, options) {
+ if (_lodash2.default.isString(funcDef)) {
+ if (!index[funcDef]) {
+ throw { message: 'Method not found ' + name };
+ }
+ funcDef = index[funcDef];
+ }
+ return new FuncInstance(funcDef, options);
+};
+
+var getFuncDef = function getFuncDef(name) {
+ return index[name];
+};
+
+var getCategories = function getCategories() {
+ return categories;
+};
+
+var foo;
+
+exports.default = foo = {
+ createFuncInstance: createFuncInstance,
+ getFuncDef: getFuncDef,
+ getCategories: getCategories
+};
+//# sourceMappingURL=dfunc.js.map
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/dfunc.js.map b/loadTest/docker/grafana/plugins/datadog/dist/test/dfunc.js.map
new file mode 100644
index 0000000..3b5db10
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/dfunc.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/dfunc.js"],"names":["index","categories","Arithmatic","Interpolation","Timeshift","Rate","Smoothing","Rollup","Rank","Count","Regression","Algorithms","addFuncDef","funcDef","params","defaultParams","append","category","push","name","type","options","optional","each","funcList","catName","sortBy","FuncInstance","def","withDefaultParams","slice","updateText","prototype","render","metricExp","str","parameters","map","value","unshift","funcRender","join","console","log","updateParam","strValue","splice","length","text","createFuncInstance","isString","message","getFuncDef","getCategories","foo"],"mappings":"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","file":"dfunc.js","sourcesContent":["import _ from 'lodash';\n\nvar index = [];\nvar categories = {\n Arithmatic: [],\n Interpolation: [],\n Timeshift: [],\n Rate: [],\n Smoothing: [],\n Rollup: [],\n Rank: [],\n Count: [],\n Regression: [],\n Algorithms: []\n};\n\nfunction addFuncDef(funcDef) {\n funcDef.params = funcDef.params || [];\n funcDef.defaultParams = funcDef.defaultParams || [];\n funcDef.append = funcDef.append || false;\n if (funcDef.category) {\n funcDef.category.push(funcDef);\n }\n index[funcDef.name] = funcDef;\n index[funcDef.name] = funcDef;\n}\n\naddFuncDef({\n name: 'abs',\n category: categories.Arithmatic,\n});\n\naddFuncDef({\n name: 'log2',\n category: categories.Arithmatic,\n});\n\naddFuncDef({\n name: 'cumsum',\n category: categories.Arithmatic,\n});\n\naddFuncDef({\n name: 'integral',\n category: categories.Arithmatic,\n});\n\naddFuncDef({\n name: 'fill',\n category: categories.Interpolation,\n params: [\n { name: \"fill\", type: 'string', options: ['null', 'zero', 'linear', 'last'] },\n { name: \"limit\", type: 'int', optional: true},\n ],\n defaultParams: ['zero'],\n append: true,\n});\n\naddFuncDef({\n name: 'hour_before',\n category: categories.Timeshift,\n});\n\naddFuncDef({\n name: 'day_before',\n category: categories.Timeshift,\n});\n\naddFuncDef({\n name: 'week_before',\n category: categories.Timeshift,\n});\n\naddFuncDef({\n name: 'month_before',\n category: categories.Timeshift,\n});\n\naddFuncDef({\n name: 'per_second',\n category: categories.Rate,\n});\n\naddFuncDef({\n name: 'per_minute',\n category: categories.Rate,\n});\n\naddFuncDef({\n name: 'per_hour',\n category: categories.Rate,\n});\n\naddFuncDef({\n name: 'dt',\n category: categories.Rate,\n});\n\naddFuncDef({\n name: 'diff',\n category: categories.Rate,\n});\n\naddFuncDef({\n name: 'derivative',\n category: categories.Rate,\n});\n\naddFuncDef({\n name: 'ewma_3',\n category: categories.Smoothing,\n});\n\naddFuncDef({\n name: 'ewma_5',\n category: categories.Smoothing,\n});\n\naddFuncDef({\n name: 'ewma_10',\n category: categories.Smoothing,\n});\n\naddFuncDef({\n name: 'ewma_20',\n category: categories.Smoothing,\n});\n\naddFuncDef({\n name: 'median_3',\n category: categories.Smoothing,\n});\n\naddFuncDef({\n name: 'median_5',\n category: categories.Smoothing,\n});\n\naddFuncDef({\n name: 'median_7',\n category: categories.Smoothing,\n});\n\naddFuncDef({\n name: 'median_9',\n category: categories.Smoothing,\n});\n\naddFuncDef({\n name: 'rollup',\n category: categories.Rollup,\n params: [\n { name: \"rollup\", type: 'string', options: ['sum', 'avg', 'min', 'max'] },\n { name: \"period\", type: 'int'},\n ],\n defaultParams: ['sum', 600],\n append: true\n});\n\naddFuncDef({\n name: 'count_nonzero',\n category: categories.Count,\n});\n\naddFuncDef({\n name: 'count_not_null',\n category: categories.Count,\n});\n\naddFuncDef({\n name: 'top',\n category: categories.Rank,\n params: [\n { name: \"top\", type: 'int', options: [5, 10, 15]},\n { name: \"aggr\", type: 'string', options: ['mean', 'min', 'max', 'area', 'l2norm', 'last']},\n { name: \"dir\", type: 'string', options: ['asc', 'desc']},\n ],\n defaultParams: [5, 'mean', 'dir'],\n});\n\naddFuncDef({\n name: 'robust_trend',\n category: categories.Regression,\n});\n\naddFuncDef({\n name: 'piecewise_constant',\n category: categories.Regression,\n});\n\naddFuncDef({\n name: 'anomalies',\n category: categories.Algorithms,\n params: [\n { name: \"anomalies\", type: 'string', options: ['basic', 'agile', 'robust', 'adaptive']},\n { name: \"bonds\", type: 'int', options: [1, 2, 3, 4, 5, 6]},\n ],\n defaultParams: ['basic', 2],\n});\n\naddFuncDef({\n name: 'anomalies',\n category: categories.Algorithms,\n params: [\n { name: \"anomalies\", type: 'string', options: ['basic', 'agile', 'robust', 'adaptive']},\n { name: \"bonds\", type: 'int', options: [1, 2, 3, 4, 5, 6]},\n ],\n defaultParams: ['basic', 2],\n});\n\naddFuncDef({\n name: 'outliers',\n category: categories.Algorithms,\n params: [\n { name: \"outliers\", type: 'string', options: ['DBSCAN', 'MAD', 'scaledDBSCAN', 'scaledMAD']},\n { name: \"tolerance\", type: 'int', options: [\n 0.33,\n 0.5,\n 1,\n 1.5,\n 2.0,\n 2.5,\n 3.0,\n 3.5,\n 4.0,\n 4.5,\n 5.0]},\n ],\n defaultParams: ['basic', 2],\n});\n\n_.each(categories, function(funcList, catName) {\n categories[catName] = _.sortBy(funcList, 'name');\n});\n\nfunction FuncInstance(funcDef, options) {\n this.def = funcDef;\n this.params = [];\n\n if (options && options.withDefaultParams) {\n this.params = funcDef.defaultParams.slice(0);\n }\n\n this.updateText();\n}\n\nFuncInstance.prototype.render = function(metricExp) {\n var str = this.def.name + '(';\n var parameters = _.map(this.params, function(value) {\n return value;\n });\n if (metricExp && !this.def.append) {\n parameters.unshift(metricExp);\n }\n\n var funcRender = str + parameters.join(', ') + ')';\n console.log(funcRender, this.def.append);\n if (this.def.append) {\n return metricExp + '.' + funcRender;\n } else {\n return funcRender;\n }\n};\n\nFuncInstance.prototype.updateParam = function(strValue, index) {\n // handle optional parameters\n // if string contains ',' and next param is optional, split and update both\n\n if (strValue === '' && this.def.params[index].optional) {\n this.params.splice(index, 1);\n }\n else {\n this.params[index] = strValue;\n }\n\n this.updateText();\n};\n\nFuncInstance.prototype.updateText = function () {\n if (this.params.length === 0) {\n this.text = this.def.name + '()';\n return;\n }\n\n var text = this.def.name + '(';\n text += this.params.join(', ');\n text += ')';\n this.text = text;\n};\n\nvar createFuncInstance = function(funcDef, options) {\n if (_.isString(funcDef)) {\n if (!index[funcDef]) {\n throw { message: 'Method not found ' + name };\n }\n funcDef = index[funcDef];\n }\n return new FuncInstance(funcDef, options);\n};\n\nvar getFuncDef = function(name) {\n return index[name];\n};\n\nvar getCategories = function() {\n return categories;\n};\n\nvar foo;\n\nexport default foo = {\n createFuncInstance: createFuncInstance,\n getFuncDef: getFuncDef,\n getCategories: getCategories,\n};\n\n"]}
\ No newline at end of file
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/func_editor.js b/loadTest/docker/grafana/plugins/datadog/dist/test/func_editor.js
new file mode 100644
index 0000000..de4d403
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/func_editor.js
@@ -0,0 +1,247 @@
+'use strict';
+
+var _angular = require('angular');
+
+var _angular2 = _interopRequireDefault(_angular);
+
+var _lodash = require('lodash');
+
+var _lodash2 = _interopRequireDefault(_lodash);
+
+var _jquery = require('jquery');
+
+var _jquery2 = _interopRequireDefault(_jquery);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+'use strict';
+
+_angular2.default.module('grafana.directives').directive('datadogFuncEditor', function ($compile, templateSrv) {
+
+ var funcSpanTemplate = '{{func.def.name}}(';
+ var paramTemplate = '';
+
+ var funcControlsTemplate = '' + '' + '' + '' + '' + '
';
+
+ return {
+ restrict: 'A',
+ link: function postLink($scope, elem) {
+ var $funcLink = (0, _jquery2.default)(funcSpanTemplate);
+ var $funcControls = (0, _jquery2.default)(funcControlsTemplate);
+ var ctrl = $scope.ctrl;
+ var func = $scope.func;
+ var funcDef = func.def;
+ var scheduledRelink = false;
+ var paramCountAtLink = 0;
+
+ function clickFuncParam(paramIndex) {
+ /*jshint validthis:true */
+
+ var $link = (0, _jquery2.default)(this);
+ var $input = $link.next();
+
+ $input.val(func.params[paramIndex]);
+ $input.css('width', $link.width() + 16 + 'px');
+
+ $link.hide();
+ $input.show();
+ $input.focus();
+ $input.select();
+
+ var typeahead = $input.data('typeahead');
+ if (typeahead) {
+ $input.val('');
+ typeahead.lookup();
+ }
+ }
+
+ function scheduledRelinkIfNeeded() {
+ if (paramCountAtLink === func.params.length) {
+ return;
+ }
+
+ if (!scheduledRelink) {
+ scheduledRelink = true;
+ setTimeout(function () {
+ relink();
+ scheduledRelink = false;
+ }, 200);
+ }
+ }
+
+ function inputBlur(paramIndex) {
+ /*jshint validthis:true */
+ var $input = (0, _jquery2.default)(this);
+ var $link = $input.prev();
+ var newValue = $input.val();
+
+ if (newValue !== '' || func.def.params[paramIndex].optional) {
+ $link.html(templateSrv.highlightVariablesAsHtml(newValue));
+
+ func.updateParam($input.val(), paramIndex);
+ scheduledRelinkIfNeeded();
+
+ $scope.$apply(function () {
+ ctrl.targetChanged();
+ });
+
+ $input.hide();
+ $link.show();
+ }
+ }
+
+ function inputKeyPress(paramIndex, e) {
+ /*jshint validthis:true */
+ if (e.which === 13) {
+ inputBlur.call(this, paramIndex);
+ }
+ }
+
+ function inputKeyDown() {
+ /*jshint validthis:true */
+ this.style.width = (3 + this.value.length) * 8 + 'px';
+ }
+
+ function addTypeahead($input, paramIndex) {
+ $input.attr('data-provide', 'typeahead');
+
+ var options = funcDef.params[paramIndex].options;
+ if (funcDef.params[paramIndex].type === 'int') {
+ options = _lodash2.default.map(options, function (val) {
+ return val.toString();
+ });
+ }
+
+ $input.typeahead({
+ source: options,
+ minLength: 0,
+ items: 20,
+ updater: function updater(value) {
+ setTimeout(function () {
+ inputBlur.call($input[0], paramIndex);
+ }, 0);
+ return value;
+ }
+ });
+
+ var typeahead = $input.data('typeahead');
+ typeahead.lookup = function () {
+ this.query = this.$element.val() || '';
+ return this.process(this.source);
+ };
+ }
+
+ function toggleFuncControls() {
+ var targetDiv = elem.closest('.tight-form');
+
+ if (elem.hasClass('show-function-controls')) {
+ elem.removeClass('show-function-controls');
+ targetDiv.removeClass('has-open-function');
+ $funcControls.hide();
+ return;
+ }
+
+ elem.addClass('show-function-controls');
+ targetDiv.addClass('has-open-function');
+
+ $funcControls.show();
+ }
+
+ function addElementsAndCompile() {
+ $funcControls.appendTo(elem);
+ $funcLink.appendTo(elem);
+
+ _lodash2.default.each(funcDef.params, function (param, index) {
+ if (param.optional && func.params.length <= index) {
+ return;
+ }
+
+ if (index > 0) {
+ (0, _jquery2.default)(', ').appendTo(elem);
+ }
+
+ var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]);
+ var $paramLink = (0, _jquery2.default)('' + paramValue + '');
+ var $input = (0, _jquery2.default)(paramTemplate);
+
+ paramCountAtLink++;
+
+ $paramLink.appendTo(elem);
+ $input.appendTo(elem);
+
+ $input.blur(_lodash2.default.partial(inputBlur, index));
+ $input.keyup(inputKeyDown);
+ $input.keypress(_lodash2.default.partial(inputKeyPress, index));
+ $paramLink.click(_lodash2.default.partial(clickFuncParam, index));
+
+ if (funcDef.params[index].options) {
+ addTypeahead($input, index);
+ }
+ });
+
+ (0, _jquery2.default)(')').appendTo(elem);
+
+ $compile(elem.contents())($scope);
+ }
+
+ function ifJustAddedFocusFistParam() {
+ if ($scope.func.added) {
+ $scope.func.added = false;
+ setTimeout(function () {
+ elem.find('.datadog-func-param-link').first().click();
+ }, 10);
+ }
+ }
+
+ function registerFuncControlsToggle() {
+ $funcLink.click(toggleFuncControls);
+ }
+
+ function registerFuncControlsActions() {
+ $funcControls.click(function (e) {
+ var $target = (0, _jquery2.default)(e.target);
+ if ($target.hasClass('fa-remove')) {
+ toggleFuncControls();
+ $scope.$apply(function () {
+ ctrl.removeFunction($scope.func);
+ });
+ return;
+ }
+
+ if ($target.hasClass('fa-arrow-left')) {
+ $scope.$apply(function () {
+ _lodash2.default.move(ctrl.functions, $scope.$index, $scope.$index - 1);
+ ctrl.targetChanged();
+ });
+ return;
+ }
+
+ if ($target.hasClass('fa-arrow-right')) {
+ $scope.$apply(function () {
+ _lodash2.default.move(ctrl.functions, $scope.$index, $scope.$index + 1);
+ ctrl.targetChanged();
+ });
+ return;
+ }
+
+ if ($target.hasClass('fa-question-circle')) {
+ window.open("http://docs.datadoghq.com/graphing/#apply-more-advanced-functions", '_blank');
+ return;
+ }
+ });
+ }
+
+ function relink() {
+ elem.children().remove();
+
+ addElementsAndCompile();
+ ifJustAddedFocusFistParam();
+ registerFuncControlsToggle();
+ registerFuncControlsActions();
+ }
+
+ relink();
+ }
+ };
+});
+//# sourceMappingURL=func_editor.js.map
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/func_editor.js.map b/loadTest/docker/grafana/plugins/datadog/dist/test/func_editor.js.map
new file mode 100644
index 0000000..7ef3c97
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/func_editor.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/func_editor.js"],"names":["module","directive","$compile","templateSrv","funcSpanTemplate","paramTemplate","funcControlsTemplate","restrict","link","postLink","$scope","elem","$funcLink","$funcControls","ctrl","func","funcDef","def","scheduledRelink","paramCountAtLink","clickFuncParam","paramIndex","$link","$input","next","val","params","css","width","hide","show","focus","select","typeahead","data","lookup","scheduledRelinkIfNeeded","length","setTimeout","relink","inputBlur","prev","newValue","optional","html","highlightVariablesAsHtml","updateParam","$apply","targetChanged","inputKeyPress","e","which","call","inputKeyDown","style","value","addTypeahead","attr","options","type","map","toString","source","minLength","items","updater","query","$element","process","toggleFuncControls","targetDiv","closest","hasClass","removeClass","addClass","addElementsAndCompile","appendTo","each","param","index","paramValue","$paramLink","blur","partial","keyup","keypress","click","contents","ifJustAddedFocusFistParam","added","find","first","registerFuncControlsToggle","registerFuncControlsActions","$target","target","removeFunction","move","functions","$index","window","open","children","remove"],"mappings":"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","file":"func_editor.js","sourcesContent":["import angular from 'angular';\nimport _ from 'lodash';\nimport $ from 'jquery';\n\n'use strict';\n\nangular\n .module('grafana.directives')\n .directive('datadogFuncEditor', function($compile, templateSrv) {\n\n var funcSpanTemplate = '{{func.def.name}}(';\n var paramTemplate = '';\n\n var funcControlsTemplate =\n '' +\n '' +\n '' +\n '' +\n '' +\n '
';\n\n return {\n restrict: 'A',\n link: function postLink($scope, elem) {\n var $funcLink = $(funcSpanTemplate);\n var $funcControls = $(funcControlsTemplate);\n var ctrl = $scope.ctrl;\n var func = $scope.func;\n var funcDef = func.def;\n var scheduledRelink = false;\n var paramCountAtLink = 0;\n\n function clickFuncParam(paramIndex) {\n /*jshint validthis:true */\n\n var $link = $(this);\n var $input = $link.next();\n\n $input.val(func.params[paramIndex]);\n $input.css('width', ($link.width() + 16) + 'px');\n\n $link.hide();\n $input.show();\n $input.focus();\n $input.select();\n\n var typeahead = $input.data('typeahead');\n if (typeahead) {\n $input.val('');\n typeahead.lookup();\n }\n }\n\n function scheduledRelinkIfNeeded() {\n if (paramCountAtLink === func.params.length) {\n return;\n }\n\n if (!scheduledRelink) {\n scheduledRelink = true;\n setTimeout(function() {\n relink();\n scheduledRelink = false;\n }, 200);\n }\n }\n\n function inputBlur(paramIndex) {\n /*jshint validthis:true */\n var $input = $(this);\n var $link = $input.prev();\n var newValue = $input.val();\n\n if (newValue !== '' || func.def.params[paramIndex].optional) {\n $link.html(templateSrv.highlightVariablesAsHtml(newValue));\n\n func.updateParam($input.val(), paramIndex);\n scheduledRelinkIfNeeded();\n\n $scope.$apply(function() {\n ctrl.targetChanged();\n });\n\n $input.hide();\n $link.show();\n }\n }\n\n function inputKeyPress(paramIndex, e) {\n /*jshint validthis:true */\n if(e.which === 13) {\n inputBlur.call(this, paramIndex);\n }\n }\n\n function inputKeyDown() {\n /*jshint validthis:true */\n this.style.width = (3 + this.value.length) * 8 + 'px';\n }\n\n function addTypeahead($input, paramIndex) {\n $input.attr('data-provide', 'typeahead');\n\n var options = funcDef.params[paramIndex].options;\n if (funcDef.params[paramIndex].type === 'int') {\n options = _.map(options, function(val) { return val.toString(); });\n }\n\n $input.typeahead({\n source: options,\n minLength: 0,\n items: 20,\n updater: function (value) {\n setTimeout(function() {\n inputBlur.call($input[0], paramIndex);\n }, 0);\n return value;\n }\n });\n\n var typeahead = $input.data('typeahead');\n typeahead.lookup = function () {\n this.query = this.$element.val() || '';\n return this.process(this.source);\n };\n }\n\n function toggleFuncControls() {\n var targetDiv = elem.closest('.tight-form');\n\n if (elem.hasClass('show-function-controls')) {\n elem.removeClass('show-function-controls');\n targetDiv.removeClass('has-open-function');\n $funcControls.hide();\n return;\n }\n\n elem.addClass('show-function-controls');\n targetDiv.addClass('has-open-function');\n\n $funcControls.show();\n }\n\n function addElementsAndCompile() {\n $funcControls.appendTo(elem);\n $funcLink.appendTo(elem);\n\n _.each(funcDef.params, function(param, index) {\n if (param.optional && func.params.length <= index) {\n return;\n }\n\n if (index > 0) {\n $(', ').appendTo(elem);\n }\n\n var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]);\n var $paramLink = $('' + paramValue + '');\n var $input = $(paramTemplate);\n\n paramCountAtLink++;\n\n $paramLink.appendTo(elem);\n $input.appendTo(elem);\n\n $input.blur(_.partial(inputBlur, index));\n $input.keyup(inputKeyDown);\n $input.keypress(_.partial(inputKeyPress, index));\n $paramLink.click(_.partial(clickFuncParam, index));\n\n if (funcDef.params[index].options) {\n addTypeahead($input, index);\n }\n\n });\n\n $(')').appendTo(elem);\n\n $compile(elem.contents())($scope);\n }\n\n function ifJustAddedFocusFistParam() {\n if ($scope.func.added) {\n $scope.func.added = false;\n setTimeout(function() {\n elem.find('.datadog-func-param-link').first().click();\n }, 10);\n }\n }\n\n function registerFuncControlsToggle() {\n $funcLink.click(toggleFuncControls);\n }\n\n function registerFuncControlsActions() {\n $funcControls.click(function(e) {\n var $target = $(e.target);\n if ($target.hasClass('fa-remove')) {\n toggleFuncControls();\n $scope.$apply(function() {\n ctrl.removeFunction($scope.func);\n });\n return;\n }\n\n if ($target.hasClass('fa-arrow-left')) {\n $scope.$apply(function() {\n _.move(ctrl.functions, $scope.$index, $scope.$index - 1);\n ctrl.targetChanged();\n });\n return;\n }\n\n if ($target.hasClass('fa-arrow-right')) {\n $scope.$apply(function() {\n _.move(ctrl.functions, $scope.$index, $scope.$index + 1);\n ctrl.targetChanged();\n });\n return;\n }\n\n if ($target.hasClass('fa-question-circle')) {\n window.open(\"http://docs.datadoghq.com/graphing/#apply-more-advanced-functions\", '_blank');\n return;\n }\n });\n }\n\n function relink() {\n elem.children().remove();\n\n addElementsAndCompile();\n ifJustAddedFocusFistParam();\n registerFuncControlsToggle();\n registerFuncControlsActions();\n }\n\n relink();\n }\n };\n\n });\n"]}
\ No newline at end of file
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/module.js b/loadTest/docker/grafana/plugins/datadog/dist/test/module.js
new file mode 100644
index 0000000..af35881
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/module.js
@@ -0,0 +1,37 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.AnnotationsQueryCtrl = exports.QueryOptionsCtrl = exports.ConfigCtrl = exports.QueryCtrl = exports.Datasource = undefined;
+
+var _datasource = require('./datasource');
+
+var _query_ctrl = require('./query_ctrl');
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var DataDogConfigCtrl = function DataDogConfigCtrl() {
+ _classCallCheck(this, DataDogConfigCtrl);
+};
+
+DataDogConfigCtrl.templateUrl = "partials/config.html";
+
+var DataDogQueryOptionsCtrl = function DataDogQueryOptionsCtrl() {
+ _classCallCheck(this, DataDogQueryOptionsCtrl);
+};
+
+DataDogQueryOptionsCtrl.templateUrl = "partials/query.options.html";
+
+var DataDogAnnotationsQueryCtrl = function DataDogAnnotationsQueryCtrl() {
+ _classCallCheck(this, DataDogAnnotationsQueryCtrl);
+};
+
+DataDogAnnotationsQueryCtrl.templateUrl = 'partials/annotations.editor.html';
+
+exports.Datasource = _datasource.DataDogDatasource;
+exports.QueryCtrl = _query_ctrl.DataDogQueryCtrl;
+exports.ConfigCtrl = DataDogConfigCtrl;
+exports.QueryOptionsCtrl = DataDogQueryOptionsCtrl;
+exports.AnnotationsQueryCtrl = DataDogAnnotationsQueryCtrl;
+//# sourceMappingURL=module.js.map
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/module.js.map b/loadTest/docker/grafana/plugins/datadog/dist/test/module.js.map
new file mode 100644
index 0000000..17cbf70
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/module.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/module.js"],"names":["DataDogConfigCtrl","templateUrl","DataDogQueryOptionsCtrl","DataDogAnnotationsQueryCtrl","Datasource","QueryCtrl","ConfigCtrl","QueryOptionsCtrl","AnnotationsQueryCtrl"],"mappings":";;;;;;;AAAA;;AACA;;;;IAEMA,iB;;;;AACNA,kBAAkBC,WAAlB,GAAgC,sBAAhC;;IAEMC,uB;;;;AACNA,wBAAwBD,WAAxB,GAAsC,6BAAtC;;IAEME,2B;;;;AACNA,4BAA4BF,WAA5B,GAA0C,kCAA1C;;QAGuBG,U;QACDC,S;QACCC,U,GAArBN,iB;QAC2BO,gB,GAA3BL,uB;QAC+BM,oB,GAA/BL,2B","file":"module.js","sourcesContent":["import {DataDogDatasource} from './datasource';\nimport {DataDogQueryCtrl} from './query_ctrl';\n\nclass DataDogConfigCtrl {}\nDataDogConfigCtrl.templateUrl = \"partials/config.html\";\n\nclass DataDogQueryOptionsCtrl {}\nDataDogQueryOptionsCtrl.templateUrl = \"partials/query.options.html\";\n\nclass DataDogAnnotationsQueryCtrl {}\nDataDogAnnotationsQueryCtrl.templateUrl = 'partials/annotations.editor.html';\n\nexport {\n DataDogDatasource as Datasource,\n DataDogQueryCtrl as QueryCtrl,\n DataDogConfigCtrl as ConfigCtrl,\n DataDogQueryOptionsCtrl as QueryOptionsCtrl,\n DataDogAnnotationsQueryCtrl as AnnotationsQueryCtrl\n};\n"]}
\ No newline at end of file
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/query_builder.js b/loadTest/docker/grafana/plugins/datadog/dist/test/query_builder.js
new file mode 100644
index 0000000..61f22f1
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/query_builder.js
@@ -0,0 +1,84 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.buildQuery = buildQuery;
+
+var _lodash = require('lodash');
+
+var _lodash2 = _interopRequireDefault(_lodash);
+
+var _dfunc = require('./dfunc');
+
+var _dfunc2 = _interopRequireDefault(_dfunc);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function buildQuery(target, adhocFilters) {
+ var aggregation = target.aggregation,
+ metric = target.metric,
+ tags = target.tags,
+ functions = target.functions;
+
+ var query = aggregation + ':' + metric;
+
+ var functionInstances = getFunctionInstances(functions);
+
+ if (tags && tags.length || adhocFilters && adhocFilters.length) {
+ query += '{';
+
+ if (tags && tags.length) {
+ query += tags.join(',');
+ }
+
+ if (adhocFilters && adhocFilters.length) {
+ var adhocTags = buildAdHocFilterString(adhocFilters);
+ if (tags && tags.length) {
+ query += ',';
+ }
+ query += adhocTags;
+ }
+
+ query += '}';
+ } else {
+ query += '{*}';
+ }
+
+ if (target.as) {
+ query += '.' + target.as + '()';
+ }
+
+ var groupedFuncs = _lodash2.default.groupBy(functionInstances, function (func) {
+ if (func.def.append) {
+ return 'appends';
+ } else {
+ return 'wraps';
+ }
+ });
+
+ _lodash2.default.each(groupedFuncs.appends, function (func) {
+ query += '.' + func.render();
+ });
+
+ _lodash2.default.each(groupedFuncs.wraps, function (func) {
+ query = func.render(query);
+ });
+
+ return query;
+}
+
+function getFunctionInstances(functions) {
+ return _lodash2.default.map(functions, function (func) {
+ var f = _dfunc2.default.createFuncInstance(func.funcDef, { withDefaultParams: false });
+ f.params = func.params.slice();
+ return f;
+ });
+}
+
+function buildAdHocFilterString(adhocFilters) {
+ return adhocFilters.map(function (filter) {
+ return filter.key + ':' + filter.value;
+ }).join(',');
+}
+//# sourceMappingURL=query_builder.js.map
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/query_builder.js.map b/loadTest/docker/grafana/plugins/datadog/dist/test/query_builder.js.map
new file mode 100644
index 0000000..a756d14
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/query_builder.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/query_builder.js"],"names":["buildQuery","target","adhocFilters","aggregation","metric","tags","functions","query","functionInstances","getFunctionInstances","length","join","adhocTags","buildAdHocFilterString","as","groupedFuncs","groupBy","func","def","append","each","appends","render","wraps","map","f","createFuncInstance","funcDef","withDefaultParams","params","slice","filter","key","value"],"mappings":";;;;;QAGgBA,U,GAAAA,U;;AAHhB;;;;AACA;;;;;;AAEO,SAASA,UAAT,CAAoBC,MAApB,EAA4BC,YAA5B,EAA0C;AAAA,MAC1CC,WAD0C,GACFF,MADE,CAC1CE,WAD0C;AAAA,MAC7BC,MAD6B,GACFH,MADE,CAC7BG,MAD6B;AAAA,MACrBC,IADqB,GACFJ,MADE,CACrBI,IADqB;AAAA,MACfC,SADe,GACFL,MADE,CACfK,SADe;;AAE/C,MAAIC,QAAWJ,WAAX,SAA0BC,MAA9B;;AAEA,MAAII,oBAAqBC,qBAAqBH,SAArB,CAAzB;;AAEA,MAAKD,QAAQA,KAAKK,MAAd,IAA0BR,gBAAgBA,aAAaQ,MAA3D,EAAoE;AAClEH,aAAS,GAAT;;AAEA,QAAIF,QAAQA,KAAKK,MAAjB,EAAyB;AACvBH,eAASF,KAAKM,IAAL,CAAU,GAAV,CAAT;AACD;;AAED,QAAIT,gBAAgBA,aAAaQ,MAAjC,EAAyC;AACvC,UAAIE,YAAYC,uBAAuBX,YAAvB,CAAhB;AACA,UAAIG,QAAQA,KAAKK,MAAjB,EAAyB;AACvBH,iBAAS,GAAT;AACD;AACDA,eAASK,SAAT;AACD;;AAEDL,aAAS,GAAT;AACD,GAhBD,MAgBO;AACLA,aAAS,KAAT;AACD;;AAED,MAAIN,OAAOa,EAAX,EAAe;AACbP,aAAS,MAAMN,OAAOa,EAAb,GAAkB,IAA3B;AACD;;AAED,MAAIC,eAAe,iBAAEC,OAAF,CAAUR,iBAAV,EAA6B,gBAAQ;AACtD,QAAIS,KAAKC,GAAL,CAASC,MAAb,EAAqB;AACnB,aAAO,SAAP;AACD,KAFD,MAEO;AACL,aAAO,OAAP;AACD;AACF,GANkB,CAAnB;;AAQA,mBAAEC,IAAF,CAAOL,aAAaM,OAApB,EAA6B,gBAAQ;AACnCd,aAAS,MAAMU,KAAKK,MAAL,EAAf;AACD,GAFD;;AAIA,mBAAEF,IAAF,CAAOL,aAAaQ,KAApB,EAA2B,gBAAQ;AACjChB,YAAQU,KAAKK,MAAL,CAAYf,KAAZ,CAAR;AACD,GAFD;;AAIA,SAAOA,KAAP;AACD;;AAED,SAASE,oBAAT,CAA8BH,SAA9B,EAAyC;AACvC,SAAO,iBAAEkB,GAAF,CAAMlB,SAAN,EAAiB,gBAAQ;AAC9B,QAAImB,IAAI,gBAAMC,kBAAN,CAAyBT,KAAKU,OAA9B,EAAuC,EAACC,mBAAmB,KAApB,EAAvC,CAAR;AACAH,MAAEI,MAAF,GAAWZ,KAAKY,MAAL,CAAYC,KAAZ,EAAX;AACA,WAAOL,CAAP;AACD,GAJM,CAAP;AAKD;;AAED,SAASZ,sBAAT,CAAgCX,YAAhC,EAA8C;AAC5C,SAAOA,aAAasB,GAAb,CAAiB,kBAAU;AAChC,WAAOO,OAAOC,GAAP,GAAa,GAAb,GAAmBD,OAAOE,KAAjC;AACD,GAFM,EAEJtB,IAFI,CAEC,GAFD,CAAP;AAGD","file":"query_builder.js","sourcesContent":["import _ from 'lodash';\nimport dfunc from './dfunc';\n\nexport function buildQuery(target, adhocFilters) {\n let {aggregation, metric, tags, functions} = target;\n let query = `${aggregation}:${metric}`;\n\n let functionInstances = getFunctionInstances(functions);\n\n if ((tags && tags.length) || (adhocFilters && adhocFilters.length)) {\n query += '{';\n\n if (tags && tags.length) {\n query += tags.join(',');\n }\n\n if (adhocFilters && adhocFilters.length) {\n let adhocTags = buildAdHocFilterString(adhocFilters);\n if (tags && tags.length) {\n query += ',';\n }\n query += adhocTags;\n }\n\n query += '}';\n } else {\n query += '{*}';\n }\n\n if (target.as) {\n query += '.' + target.as + '()';\n }\n\n var groupedFuncs = _.groupBy(functionInstances, func => {\n if (func.def.append) {\n return 'appends';\n } else {\n return 'wraps';\n }\n });\n\n _.each(groupedFuncs.appends, func => {\n query += '.' + func.render();\n });\n\n _.each(groupedFuncs.wraps, func => {\n query = func.render(query);\n });\n\n return query;\n}\n\nfunction getFunctionInstances(functions) {\n return _.map(functions, func => {\n var f = dfunc.createFuncInstance(func.funcDef, {withDefaultParams: false});\n f.params = func.params.slice();\n return f;\n });\n}\n\nfunction buildAdHocFilterString(adhocFilters) {\n return adhocFilters.map(filter => {\n return filter.key + ':' + filter.value;\n }).join(',');\n}\n"]}
\ No newline at end of file
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/query_ctrl.js b/loadTest/docker/grafana/plugins/datadog/dist/test/query_ctrl.js
new file mode 100644
index 0000000..f71073a
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/query_ctrl.js
@@ -0,0 +1,230 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.DataDogQueryCtrl = undefined;
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _lodash = require('lodash');
+
+var _lodash2 = _interopRequireDefault(_lodash);
+
+var _dfunc = require('./dfunc');
+
+var _dfunc2 = _interopRequireDefault(_dfunc);
+
+var _sdk = require('app/plugins/sdk');
+
+require('./func_editor');
+
+require('./add_datadog_func');
+
+var _query_builder = require('./query_builder');
+
+var queryBuilder = _interopRequireWildcard(_query_builder);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var DataDogQueryCtrl = exports.DataDogQueryCtrl = function (_QueryCtrl) {
+ _inherits(DataDogQueryCtrl, _QueryCtrl);
+
+ function DataDogQueryCtrl($scope, $injector, uiSegmentSrv, templateSrv) {
+ _classCallCheck(this, DataDogQueryCtrl);
+
+ var _this = _possibleConstructorReturn(this, (DataDogQueryCtrl.__proto__ || Object.getPrototypeOf(DataDogQueryCtrl)).call(this, $scope, $injector));
+
+ _this.removeText = '-- remove tag --';
+ _this.uiSegmentSrv = uiSegmentSrv;
+ _this.templateSrv = templateSrv;
+
+ if (_this.target.aggregation) {
+ _this.aggregationSegment = new uiSegmentSrv.newSegment(_this.target.aggregation);
+ } else {
+ _this.aggregationSegment = new uiSegmentSrv.newSegment({
+ value: 'Select Aggregation',
+ fake: true,
+ custom: false
+ });
+ }
+
+ if (_this.target.metric) {
+ _this.metricSegment = new uiSegmentSrv.newSegment(_this.target.metric);
+ } else {
+ _this.metricSegment = new uiSegmentSrv.newSegment({
+ value: 'Select Metric',
+ fake: true,
+ custom: false
+ });
+ }
+
+ _this.target.tags = _this.target.tags || [];
+ _this.tagSegments = _this.target.tags.map(uiSegmentSrv.newSegment);
+ _this.fixTagSegments();
+
+ _this.functions = [];
+ _this.target.functions = _this.target.functions || [];
+ _this.functions = _lodash2.default.map(_this.target.functions, function (func) {
+ var f = _dfunc2.default.createFuncInstance(func.funcDef, { withDefaultParams: false });
+ f.params = func.params.slice();
+ return f;
+ });
+
+ if (_this.target.as) {
+ _this.asSegment = uiSegmentSrv.newSegment(_this.target.as);
+ } else {
+ _this.asSegment = uiSegmentSrv.newSegment({
+ value: 'Select As',
+ fake: true,
+ custom: false
+ });
+ }
+
+ return _this;
+ }
+
+ _createClass(DataDogQueryCtrl, [{
+ key: 'toggleEditorMode',
+ value: function toggleEditorMode() {
+ this.target.rawQuery = !this.target.rawQuery;
+ }
+ }, {
+ key: 'getMetrics',
+ value: function getMetrics() {
+ return this.datasource.metricFindQuery().then(this.uiSegmentSrv.transformToSegments(true));
+ }
+ }, {
+ key: 'getAggregations',
+ value: function getAggregations() {
+ return Promise.resolve([{ text: 'avg by', value: 'avg' }, { text: 'max by', value: 'max' }, { text: 'min by', value: 'min' }, { text: 'sub by', value: 'sum' }]);
+ }
+ }, {
+ key: 'getAs',
+ value: function getAs() {
+ return Promise.resolve([{ text: 'None', value: 'None' }, { text: 'as_count', value: 'as_count' }, { text: 'as_rate', value: 'as_rate' }]);
+ }
+ }, {
+ key: 'getTags',
+ value: function getTags(segment) {
+ var _this2 = this;
+
+ return this.datasource.tagFindQuery().then(this.uiSegmentSrv.transformToSegments(true)).then(function (results) {
+ if (segment.type !== 'plus-button') {
+ var removeSegment = _this2.uiSegmentSrv.newFake(_this2.removeText);
+ results.unshift(removeSegment);
+ }
+
+ return results;
+ });
+ }
+ }, {
+ key: 'aggregationChanged',
+ value: function aggregationChanged() {
+ this.target.aggregation = this.aggregationSegment.value;
+ this.panelCtrl.refresh();
+ }
+ }, {
+ key: 'metricChanged',
+ value: function metricChanged() {
+ this.target.metric = this.metricSegment.value;
+ this.panelCtrl.refresh();
+ }
+ }, {
+ key: 'asChanged',
+ value: function asChanged() {
+ if (this.asSegment.value === 'None') {
+ this.target.as = null;
+ } else {
+ this.target.as = this.asSegment.value;
+ }
+ this.panelCtrl.refresh();
+ }
+ }, {
+ key: 'fixTagSegments',
+ value: function fixTagSegments() {
+ var count = this.tagSegments.length;
+ var lastSegment = this.tagSegments[Math.max(count - 1, 0)];
+
+ if (!lastSegment || lastSegment.type !== 'plus-button') {
+ this.tagSegments.push(this.uiSegmentSrv.newPlusButton());
+ }
+ }
+ }, {
+ key: 'targetChanged',
+ value: function targetChanged() {
+ if (this.error) {
+ return;
+ }
+
+ this.panelCtrl.refresh();
+ }
+ }, {
+ key: 'persistFunctions',
+ value: function persistFunctions() {
+ this.target.functions = _lodash2.default.map(this.functions, function (func) {
+ return {
+ funcDef: func.def.name,
+ params: func.params.slice()
+ };
+ });
+ }
+ }, {
+ key: 'removeFunction',
+ value: function removeFunction(func) {
+ this.functions = _lodash2.default.without(this.functions, func);
+ this.persistFunctions();
+ this.targetChanged();
+ }
+ }, {
+ key: 'addFunction',
+ value: function addFunction(funcDef) {
+ var func = _dfunc2.default.createFuncInstance(funcDef, { withDefaultParams: true });
+ func.added = true;
+ this.functions.push(func);
+ this.persistFunctions();
+ this.targetChanged();
+ }
+ }, {
+ key: 'tagSegmentUpdated',
+ value: function tagSegmentUpdated(segment, index) {
+ if (segment.value === this.removeText) {
+ this.tagSegments.splice(index, 1);
+ }
+
+ var realSegments = _lodash2.default.filter(this.tagSegments, function (segment) {
+ return segment.value;
+ });
+ this.target.tags = realSegments.map(function (segment) {
+ return segment.value;
+ });
+
+ this.tagSegments = _lodash2.default.map(this.target.tags, this.uiSegmentSrv.newSegment);
+ this.fixTagSegments();
+
+ this.panelCtrl.refresh();
+ }
+ }, {
+ key: 'getCollapsedText',
+ value: function getCollapsedText() {
+ if (this.target.rawQuery) {
+ return this.target.query;
+ } else {
+ return queryBuilder.buildQuery(this.target);
+ }
+ }
+ }]);
+
+ return DataDogQueryCtrl;
+}(_sdk.QueryCtrl);
+
+DataDogQueryCtrl.templateUrl = 'partials/query.editor.html';
+//# sourceMappingURL=query_ctrl.js.map
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/query_ctrl.js.map b/loadTest/docker/grafana/plugins/datadog/dist/test/query_ctrl.js.map
new file mode 100644
index 0000000..9637c99
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/query_ctrl.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/query_ctrl.js"],"names":["queryBuilder","DataDogQueryCtrl","$scope","$injector","uiSegmentSrv","templateSrv","removeText","target","aggregation","aggregationSegment","newSegment","value","fake","custom","metric","metricSegment","tags","tagSegments","map","fixTagSegments","functions","f","createFuncInstance","func","funcDef","withDefaultParams","params","slice","as","asSegment","rawQuery","datasource","metricFindQuery","then","transformToSegments","Promise","resolve","text","segment","tagFindQuery","type","removeSegment","newFake","results","unshift","panelCtrl","refresh","count","length","lastSegment","Math","max","push","newPlusButton","error","def","name","without","persistFunctions","targetChanged","added","index","splice","realSegments","filter","query","buildQuery","templateUrl"],"mappings":";;;;;;;;;AAAA;;;;AACA;;;;AACA;;AACA;;AACA;;AACA;;IAAYA,Y;;;;;;;;;;;;IAECC,gB,WAAAA,gB;;;AAEX,4BAAYC,MAAZ,EAAoBC,SAApB,EAA+BC,YAA/B,EAA6CC,WAA7C,EAA2D;AAAA;;AAAA,oIACnDH,MADmD,EAC3CC,SAD2C;;AAEzD,UAAKG,UAAL,GAAkB,kBAAlB;AACA,UAAKF,YAAL,GAAoBA,YAApB;AACA,UAAKC,WAAL,GAAmBA,WAAnB;;AAEA,QAAI,MAAKE,MAAL,CAAYC,WAAhB,EAA6B;AAC3B,YAAKC,kBAAL,GAA0B,IAAIL,aAAaM,UAAjB,CACxB,MAAKH,MAAL,CAAYC,WADY,CAA1B;AAGD,KAJD,MAIO;AACL,YAAKC,kBAAL,GAA0B,IAAIL,aAAaM,UAAjB,CAA4B;AACpDC,eAAO,oBAD6C;AAEpDC,cAAM,IAF8C;AAGpDC,gBAAQ;AAH4C,OAA5B,CAA1B;AAKD;;AAED,QAAI,MAAKN,MAAL,CAAYO,MAAhB,EAAwB;AACtB,YAAKC,aAAL,GAAqB,IAAIX,aAAaM,UAAjB,CACnB,MAAKH,MAAL,CAAYO,MADO,CAArB;AAGD,KAJD,MAIO;AACL,YAAKC,aAAL,GAAqB,IAAIX,aAAaM,UAAjB,CAA4B;AAC/CC,eAAO,eADwC;AAE/CC,cAAM,IAFyC;AAG/CC,gBAAQ;AAHuC,OAA5B,CAArB;AAKD;;AAED,UAAKN,MAAL,CAAYS,IAAZ,GAAmB,MAAKT,MAAL,CAAYS,IAAZ,IAAoB,EAAvC;AACA,UAAKC,WAAL,GAAmB,MAAKV,MAAL,CAAYS,IAAZ,CAAiBE,GAAjB,CAAqBd,aAAaM,UAAlC,CAAnB;AACA,UAAKS,cAAL;;AAEA,UAAKC,SAAL,GAAiB,EAAjB;AACA,UAAKb,MAAL,CAAYa,SAAZ,GAAwB,MAAKb,MAAL,CAAYa,SAAZ,IAAyB,EAAjD;AACA,UAAKA,SAAL,GAAiB,iBAAEF,GAAF,CAAM,MAAKX,MAAL,CAAYa,SAAlB,EAA6B,gBAAQ;AACpD,UAAIC,IAAI,gBAAMC,kBAAN,CAAyBC,KAAKC,OAA9B,EAAuC,EAACC,mBAAmB,KAApB,EAAvC,CAAR;AACAJ,QAAEK,MAAF,GAAWH,KAAKG,MAAL,CAAYC,KAAZ,EAAX;AACA,aAAON,CAAP;AACD,KAJgB,CAAjB;;AAMA,QAAI,MAAKd,MAAL,CAAYqB,EAAhB,EAAoB;AAClB,YAAKC,SAAL,GAAiBzB,aAAaM,UAAb,CAAwB,MAAKH,MAAL,CAAYqB,EAApC,CAAjB;AACD,KAFD,MAEO;AACL,YAAKC,SAAL,GAAiBzB,aAAaM,UAAb,CAAwB;AACvCC,eAAO,WADgC;AAEvCC,cAAM,IAFiC;AAGvCC,gBAAQ;AAH+B,OAAxB,CAAjB;AAKD;;AAlDwD;AAoD1D;;;;uCAEkB;AACjB,WAAKN,MAAL,CAAYuB,QAAZ,GAAuB,CAAC,KAAKvB,MAAL,CAAYuB,QAApC;AACD;;;iCAEY;AACX,aAAO,KAAKC,UAAL,CAAgBC,eAAhB,GACNC,IADM,CACD,KAAK7B,YAAL,CAAkB8B,mBAAlB,CAAsC,IAAtC,CADC,CAAP;AAED;;;sCAEiB;AAChB,aAAOC,QAAQC,OAAR,CAAgB,CACrB,EAACC,MAAM,QAAP,EAAiB1B,OAAO,KAAxB,EADqB,EAErB,EAAC0B,MAAM,QAAP,EAAiB1B,OAAO,KAAxB,EAFqB,EAGrB,EAAC0B,MAAM,QAAP,EAAiB1B,OAAO,KAAxB,EAHqB,EAIrB,EAAC0B,MAAM,QAAP,EAAiB1B,OAAO,KAAxB,EAJqB,CAAhB,CAAP;AAMD;;;4BAEO;AACN,aAAOwB,QAAQC,OAAR,CAAgB,CACrB,EAACC,MAAM,MAAP,EAAe1B,OAAO,MAAtB,EADqB,EAErB,EAAC0B,MAAM,UAAP,EAAmB1B,OAAO,UAA1B,EAFqB,EAGrB,EAAC0B,MAAM,SAAP,EAAkB1B,OAAO,SAAzB,EAHqB,CAAhB,CAAP;AAKD;;;4BAEO2B,O,EAAS;AAAA;;AACf,aAAO,KAAKP,UAAL,CAAgBQ,YAAhB,GACNN,IADM,CACD,KAAK7B,YAAL,CAAkB8B,mBAAlB,CAAsC,IAAtC,CADC,EAEND,IAFM,CAED,mBAAW;AACf,YAAIK,QAAQE,IAAR,KAAiB,aAArB,EAAoC;AAClC,cAAIC,gBAAgB,OAAKrC,YAAL,CAAkBsC,OAAlB,CAA0B,OAAKpC,UAA/B,CAApB;AACAqC,kBAAQC,OAAR,CAAgBH,aAAhB;AACD;;AAED,eAAOE,OAAP;AACD,OATM,CAAP;AAUD;;;yCAEoB;AACnB,WAAKpC,MAAL,CAAYC,WAAZ,GAA0B,KAAKC,kBAAL,CAAwBE,KAAlD;AACA,WAAKkC,SAAL,CAAeC,OAAf;AACD;;;oCAEe;AACd,WAAKvC,MAAL,CAAYO,MAAZ,GAAqB,KAAKC,aAAL,CAAmBJ,KAAxC;AACA,WAAKkC,SAAL,CAAeC,OAAf;AACD;;;gCAEW;AACV,UAAI,KAAKjB,SAAL,CAAelB,KAAf,KAAyB,MAA7B,EAAqC;AACnC,aAAKJ,MAAL,CAAYqB,EAAZ,GAAiB,IAAjB;AACD,OAFD,MAEO;AACL,aAAKrB,MAAL,CAAYqB,EAAZ,GAAiB,KAAKC,SAAL,CAAelB,KAAhC;AACD;AACD,WAAKkC,SAAL,CAAeC,OAAf;AACD;;;qCAEgB;AACf,UAAIC,QAAQ,KAAK9B,WAAL,CAAiB+B,MAA7B;AACA,UAAIC,cAAc,KAAKhC,WAAL,CAAiBiC,KAAKC,GAAL,CAASJ,QAAM,CAAf,EAAkB,CAAlB,CAAjB,CAAlB;;AAEA,UAAI,CAACE,WAAD,IAAgBA,YAAYT,IAAZ,KAAqB,aAAzC,EAAwD;AACtD,aAAKvB,WAAL,CAAiBmC,IAAjB,CAAsB,KAAKhD,YAAL,CAAkBiD,aAAlB,EAAtB;AACD;AACF;;;oCAEe;AACd,UAAI,KAAKC,KAAT,EAAgB;AACd;AACD;;AAED,WAAKT,SAAL,CAAeC,OAAf;AACD;;;uCAEmB;AAClB,WAAKvC,MAAL,CAAYa,SAAZ,GAAwB,iBAAEF,GAAF,CAAM,KAAKE,SAAX,EAAsB,gBAAQ;AACpD,eAAO;AACLI,mBAASD,KAAKgC,GAAL,CAASC,IADb;AAEL9B,kBAAQH,KAAKG,MAAL,CAAYC,KAAZ;AAFH,SAAP;AAID,OALuB,CAAxB;AAMD;;;mCAEcJ,I,EAAM;AACnB,WAAKH,SAAL,GAAiB,iBAAEqC,OAAF,CAAU,KAAKrC,SAAf,EAA0BG,IAA1B,CAAjB;AACA,WAAKmC,gBAAL;AACA,WAAKC,aAAL;AACD;;;gCAEWnC,O,EAAS;AACnB,UAAID,OAAO,gBAAMD,kBAAN,CAAyBE,OAAzB,EAAkC,EAACC,mBAAmB,IAApB,EAAlC,CAAX;AACAF,WAAKqC,KAAL,GAAa,IAAb;AACA,WAAKxC,SAAL,CAAegC,IAAf,CAAoB7B,IAApB;AACA,WAAKmC,gBAAL;AACA,WAAKC,aAAL;AACD;;;sCAEiBrB,O,EAASuB,K,EAAO;AAChC,UAAIvB,QAAQ3B,KAAR,KAAkB,KAAKL,UAA3B,EAAuC;AACrC,aAAKW,WAAL,CAAiB6C,MAAjB,CAAwBD,KAAxB,EAA+B,CAA/B;AACD;;AAED,UAAIE,eAAe,iBAAEC,MAAF,CAAS,KAAK/C,WAAd,EAA2B;AAAA,eAAWqB,QAAQ3B,KAAnB;AAAA,OAA3B,CAAnB;AACA,WAAKJ,MAAL,CAAYS,IAAZ,GAAmB+C,aAAa7C,GAAb,CAAiB;AAAA,eAAWoB,QAAQ3B,KAAnB;AAAA,OAAjB,CAAnB;;AAEA,WAAKM,WAAL,GAAmB,iBAAEC,GAAF,CAAM,KAAKX,MAAL,CAAYS,IAAlB,EAAwB,KAAKZ,YAAL,CAAkBM,UAA1C,CAAnB;AACA,WAAKS,cAAL;;AAEA,WAAK0B,SAAL,CAAeC,OAAf;AACD;;;uCAEkB;AACjB,UAAI,KAAKvC,MAAL,CAAYuB,QAAhB,EAA0B;AACxB,eAAO,KAAKvB,MAAL,CAAY0D,KAAnB;AACD,OAFD,MAEO;AACL,eAAOjE,aAAakE,UAAb,CAAwB,KAAK3D,MAA7B,CAAP;AACD;AACF;;;;;;AAGHN,iBAAiBkE,WAAjB,GAA+B,4BAA/B","file":"query_ctrl.js","sourcesContent":["import _ from 'lodash';\nimport dfunc from './dfunc';\nimport {QueryCtrl} from 'app/plugins/sdk';\nimport './func_editor';\nimport './add_datadog_func';\nimport * as queryBuilder from './query_builder';\n\nexport class DataDogQueryCtrl extends QueryCtrl {\n\n constructor($scope, $injector, uiSegmentSrv, templateSrv) {\n super($scope, $injector);\n this.removeText = '-- remove tag --';\n this.uiSegmentSrv = uiSegmentSrv;\n this.templateSrv = templateSrv;\n\n if (this.target.aggregation) {\n this.aggregationSegment = new uiSegmentSrv.newSegment(\n this.target.aggregation\n );\n } else {\n this.aggregationSegment = new uiSegmentSrv.newSegment({\n value: 'Select Aggregation',\n fake: true,\n custom: false,\n });\n }\n\n if (this.target.metric) {\n this.metricSegment = new uiSegmentSrv.newSegment(\n this.target.metric\n );\n } else {\n this.metricSegment = new uiSegmentSrv.newSegment({\n value: 'Select Metric',\n fake: true,\n custom: false,\n });\n }\n\n this.target.tags = this.target.tags || [];\n this.tagSegments = this.target.tags.map(uiSegmentSrv.newSegment);\n this.fixTagSegments();\n\n this.functions = [];\n this.target.functions = this.target.functions || [];\n this.functions = _.map(this.target.functions, func => {\n var f = dfunc.createFuncInstance(func.funcDef, {withDefaultParams: false});\n f.params = func.params.slice();\n return f;\n });\n\n if (this.target.as) {\n this.asSegment = uiSegmentSrv.newSegment(this.target.as);\n } else {\n this.asSegment = uiSegmentSrv.newSegment({\n value: 'Select As',\n fake: true,\n custom: false,\n });\n }\n\n }\n\n toggleEditorMode() {\n this.target.rawQuery = !this.target.rawQuery;\n }\n\n getMetrics() {\n return this.datasource.metricFindQuery()\n .then(this.uiSegmentSrv.transformToSegments(true));\n }\n\n getAggregations() {\n return Promise.resolve([\n {text: 'avg by', value: 'avg'},\n {text: 'max by', value: 'max'},\n {text: 'min by', value: 'min'},\n {text: 'sub by', value: 'sum'},\n ]);\n }\n\n getAs() {\n return Promise.resolve([\n {text: 'None', value: 'None'},\n {text: 'as_count', value: 'as_count'},\n {text: 'as_rate', value: 'as_rate'},\n ]);\n }\n\n getTags(segment) {\n return this.datasource.tagFindQuery()\n .then(this.uiSegmentSrv.transformToSegments(true))\n .then(results => {\n if (segment.type !== 'plus-button') {\n let removeSegment = this.uiSegmentSrv.newFake(this.removeText);\n results.unshift(removeSegment);\n }\n\n return results;\n });\n }\n\n aggregationChanged() {\n this.target.aggregation = this.aggregationSegment.value;\n this.panelCtrl.refresh();\n }\n\n metricChanged() {\n this.target.metric = this.metricSegment.value;\n this.panelCtrl.refresh();\n }\n\n asChanged() {\n if (this.asSegment.value === 'None') {\n this.target.as = null;\n } else {\n this.target.as = this.asSegment.value;\n }\n this.panelCtrl.refresh();\n }\n\n fixTagSegments() {\n var count = this.tagSegments.length;\n var lastSegment = this.tagSegments[Math.max(count-1, 0)];\n\n if (!lastSegment || lastSegment.type !== 'plus-button') {\n this.tagSegments.push(this.uiSegmentSrv.newPlusButton());\n }\n }\n\n targetChanged() {\n if (this.error) {\n return;\n }\n\n this.panelCtrl.refresh();\n }\n\n persistFunctions () {\n this.target.functions = _.map(this.functions, func => {\n return {\n funcDef: func.def.name,\n params: func.params.slice(),\n };\n });\n }\n\n removeFunction(func) {\n this.functions = _.without(this.functions, func);\n this.persistFunctions();\n this.targetChanged();\n }\n\n addFunction(funcDef) {\n var func = dfunc.createFuncInstance(funcDef, {withDefaultParams: true});\n func.added = true;\n this.functions.push(func);\n this.persistFunctions();\n this.targetChanged();\n }\n\n tagSegmentUpdated(segment, index) {\n if (segment.value === this.removeText) {\n this.tagSegments.splice(index, 1);\n }\n\n let realSegments = _.filter(this.tagSegments, segment => segment.value);\n this.target.tags = realSegments.map(segment => segment.value);\n\n this.tagSegments = _.map(this.target.tags, this.uiSegmentSrv.newSegment);\n this.fixTagSegments();\n\n this.panelCtrl.refresh();\n }\n\n getCollapsedText() {\n if (this.target.rawQuery) {\n return this.target.query;\n } else {\n return queryBuilder.buildQuery(this.target);\n }\n }\n}\n\nDataDogQueryCtrl.templateUrl = 'partials/query.editor.html';\n"]}
\ No newline at end of file
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/showdown.min.js b/loadTest/docker/grafana/plugins/datadog/dist/test/showdown.min.js
new file mode 100644
index 0000000..03e7446
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/showdown.min.js
@@ -0,0 +1,5 @@
+/*! showdown 29-01-2017 */
+
+(function(){function a(a){"use strict";var b={omitExtraWLInCodeBlocks:{defaultValue:!1,describe:"Omit the default extra whiteline added to code blocks",type:"boolean"},noHeaderId:{defaultValue:!1,describe:"Turn on/off generated header id",type:"boolean"},prefixHeaderId:{defaultValue:!1,describe:"Specify a prefix to generated header ids",type:"string"},ghCompatibleHeaderId:{defaultValue:!1,describe:"Generate header ids compatible with github style (spaces are replaced with dashes, a bunch of non alphanumeric chars are removed)",type:"boolean"},headerLevelStart:{defaultValue:!1,describe:"The header blocks level start",type:"integer"},parseImgDimensions:{defaultValue:!1,describe:"Turn on/off image dimension parsing",type:"boolean"},simplifiedAutoLink:{defaultValue:!1,describe:"Turn on/off GFM autolink style",type:"boolean"},excludeTrailingPunctuationFromURLs:{defaultValue:!1,describe:"Excludes trailing punctuation from links generated with autoLinking",type:"boolean"},literalMidWordUnderscores:{defaultValue:!1,describe:"Parse midword underscores as literal underscores",type:"boolean"},strikethrough:{defaultValue:!1,describe:"Turn on/off strikethrough support",type:"boolean"},tables:{defaultValue:!1,describe:"Turn on/off tables support",type:"boolean"},tablesHeaderId:{defaultValue:!1,describe:"Add an id to table headers",type:"boolean"},ghCodeBlocks:{defaultValue:!0,describe:"Turn on/off GFM fenced code blocks support",type:"boolean"},tasklists:{defaultValue:!1,describe:"Turn on/off GFM tasklist support",type:"boolean"},smoothLivePreview:{defaultValue:!1,describe:"Prevents weird effects in live previews due to incomplete input",type:"boolean"},smartIndentationFix:{defaultValue:!1,description:"Tries to smartly fix indentation in es6 strings",type:"boolean"},disableForced4SpacesIndentedSublists:{defaultValue:!1,description:"Disables the requirement of indenting nested sublists by 4 spaces",type:"boolean"},simpleLineBreaks:{defaultValue:!1,description:"Parses simple line breaks as
(GFM Style)",type:"boolean"},requireSpaceBeforeHeadingText:{defaultValue:!1,description:"Makes adding a space between `#` and the header text mandatory (GFM Style)",type:"boolean"},ghMentions:{defaultValue:!1,description:"Enables github @mentions",type:"boolean"},ghMentionsLink:{defaultValue:"https://github.com/{u}",description:"Changes the link generated by @mentions. Only applies if ghMentions option is enabled.",type:"string"},encodeEmails:{defaultValue:!0,description:"Encode e-mail addresses through the use of Character Entities, transforming ASCII e-mail addresses into its equivalent decimal entities",type:"boolean"}};if(a===!1)return JSON.parse(JSON.stringify(b));var c={};for(var d in b)b.hasOwnProperty(d)&&(c[d]=b[d].defaultValue);return c}function b(){"use strict";var b=a(!0),c={};for(var d in b)b.hasOwnProperty(d)&&(c[d]=!0);return c}function c(a,b){"use strict";var c=b?"Error in "+b+" extension->":"Error in unnamed extension",d={valid:!0,error:""};e.helper.isArray(a)||(a=[a]);for(var f=0;f-1,l=new RegExp(b+"|"+c,"g"+j.replace(/g/g,"")),m=new RegExp(b,j.replace(/g/g,"")),n=[];do for(e=0;g=l.exec(a);)if(m.test(g[0]))e++||(f=l.lastIndex,h=f-g[0].length);else if(e&&!--e){i=g.index+g[0].length;var o={left:{start:h,end:f},match:{start:f,end:g.index},right:{start:g.index,end:i},wholeMatch:{start:h,end:i}};if(n.push(o),!k)return n}while(e&&(l.lastIndex=f));return n};e.helper.matchRecursiveRegExp=function(a,b,c,d){"use strict";for(var e=k(a,b,c,d),f=[],g=0;g0){var l=[];0!==h[0].wholeMatch.start&&l.push(a.slice(0,h[0].wholeMatch.start));for(var m=0;j>m;++m)l.push(b(a.slice(h[m].wholeMatch.start,h[m].wholeMatch.end),a.slice(h[m].match.start,h[m].match.end),a.slice(h[m].left.start,h[m].left.end),a.slice(h[m].right.start,h[m].right.end))),j-1>m&&l.push(a.slice(h[m].wholeMatch.end,h[m+1].wholeMatch.start));h[j-1].wholeMatch.end.9?b[2](a):c>.45?b[1](a):b[0](a)}return a})},"undefined"==typeof console&&(console={warn:function(a){"use strict";alert(a)},log:function(a){"use strict";alert(a)},error:function(a){"use strict";throw a}}),e.helper.regexes={asteriskAndDash:/([*_])/g},e.Converter=function(a){"use strict";function b(){a=a||{};for(var b in h)h.hasOwnProperty(b)&&(m[b]=h[b]);if("object"!=typeof a)throw Error("Converter expects the passed parameter to be an object, but "+typeof a+" was passed instead.");for(var c in a)a.hasOwnProperty(c)&&(m[c]=a[c]);m.extensions&&e.helper.forEach(m.extensions,d)}function d(a,b){if(b=b||null,e.helper.isString(a)){if(a=e.helper.stdExtName(a),b=a,e.extensions[a])return console.warn("DEPRECATION WARNING: "+a+" is an old extension that uses a deprecated loading method.Please inform the developer that the extension should be updated!"),void f(e.extensions[a],a);if(e.helper.isUndefined(g[a]))throw Error('Extension "'+a+'" could not be loaded. It was either not found or is not a valid extension.');a=g[a]}"function"==typeof a&&(a=a()),e.helper.isArray(a)||(a=[a]);var d=c(a,b);if(!d.valid)throw Error(d.error);for(var h=0;h-1))return a;m=""}else m=c.gUrls[l],e.helper.isUndefined(c.gTitles[l])||(n=c.gTitles[l]);m=m.replace(e.helper.regexes.asteriskAndDash,e.helper.escapeCharactersCallback);var o='"+k+""};return a=a.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)][ ]?(?:\n[ ]*)?\[(.*?)])()()()()/g,d),a=a.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)]\([ \t]*()(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,d),a=a.replace(/(\[([^\[\]]+)])()()()()()/g,d),b.ghMentions&&(a=a.replace(/(^|\s)(\\)?(@([a-z\d\-]+))(?=[.!?;,[\]()]|\s|$)/gim,function(a,c,d,f,g){if("\\"===d)return c+f;if(!e.helper.isString(b.ghMentionsLink))throw new Error("ghMentionsLink option must be a string");var h=b.ghMentionsLink.replace(/\{u}/g,g);return c+''+f+""})),a=c.converter._dispatch("anchors.after",a,b,c)}),e.subParser("autoLinks",function(a,b,c){"use strict";function d(a,c,d,e,f){var g=c,h="";return/^www\./i.test(c)&&(c=c.replace(/^www\./i,"http://www.")),b.excludeTrailingPunctuationFromURLs&&f&&(h=f),''+g+""+h}function f(a,c,d){var f="mailto:";return c=c||"",d=e.subParser("unescapeSpecialChars")(d),b.encodeEmails?(d=e.helper.encodeEmailAddress(d),f=e.helper.encodeEmailAddress(f+d)):f+=d,c+''+d+""}a=c.converter._dispatch("autoLinks.before",a,b,c);var g=/\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)()(?=\s|$)(?!["<>])/gi,h=/\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+?)([.!?()]?)(?=\s|$)(?!["<>])/gi,i=/<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi,j=/(^|\s)(?:mailto:)?([A-Za-z0-9!#$%&'*+-\/=?^_`{|}~.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?=$|\s)/gim,k=/<()(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi;return a=a.replace(i,d),a=a.replace(k,f),b.simplifiedAutoLink&&(a=b.excludeTrailingPunctuationFromURLs?a.replace(h,d):a.replace(g,d),a=a.replace(j,f)),a=c.converter._dispatch("autoLinks.after",a,b,c)}),e.subParser("blockGamut",function(a,b,c){"use strict";return a=c.converter._dispatch("blockGamut.before",a,b,c),a=e.subParser("blockQuotes")(a,b,c),a=e.subParser("headers")(a,b,c),a=e.subParser("horizontalRule")(a,b,c),a=e.subParser("lists")(a,b,c),a=e.subParser("codeBlocks")(a,b,c),a=e.subParser("tables")(a,b,c),a=e.subParser("hashHTMLBlocks")(a,b,c),a=e.subParser("paragraphs")(a,b,c),a=c.converter._dispatch("blockGamut.after",a,b,c)}),e.subParser("blockQuotes",function(a,b,c){"use strict";return a=c.converter._dispatch("blockQuotes.before",a,b,c),a=a.replace(/((^ {0,3}>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(a,d){var f=d;return f=f.replace(/^[ \t]*>[ \t]?/gm,"¨0"),f=f.replace(/¨0/g,""),f=f.replace(/^[ \t]+$/gm,""),f=e.subParser("githubCodeBlocks")(f,b,c),f=e.subParser("blockGamut")(f,b,c),f=f.replace(/(^|\n)/g,"$1 "),f=f.replace(/(\s*[^\r]+?<\/pre>)/gm,function(a,b){var c=b;return c=c.replace(/^ /gm,"¨0"),c=c.replace(/¨0/g,"")}),e.subParser("hashBlock")("\n"+f+"\n
",b,c)}),a=c.converter._dispatch("blockQuotes.after",a,b,c)}),e.subParser("codeBlocks",function(a,b,c){"use strict";a=c.converter._dispatch("codeBlocks.before",a,b,c),a+="¨0";var d=/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=¨0))/g;return a=a.replace(d,function(a,d,f){var g=d,h=f,i="\n";return g=e.subParser("outdent")(g),g=e.subParser("encodeCode")(g),g=e.subParser("detab")(g),g=g.replace(/^\n+/g,""),g=g.replace(/\n+$/g,""),b.omitExtraWLInCodeBlocks&&(i=""),g=""+g+i+"
",e.subParser("hashBlock")(g,b,c)+h}),a=a.replace(/¨0/,""),a=c.converter._dispatch("codeBlocks.after",a,b,c)}),e.subParser("codeSpans",function(a,b,c){"use strict";return a=c.converter._dispatch("codeSpans.before",a,b,c),"undefined"==typeof a&&(a=""),a=a.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(a,b,c,d){var f=d;return f=f.replace(/^([ \t]*)/g,""),f=f.replace(/[ \t]*$/g,""),f=e.subParser("encodeCode")(f),b+""+f+""}),a=c.converter._dispatch("codeSpans.after",a,b,c)}),e.subParser("detab",function(a){"use strict";return a=a.replace(/\t(?=\t)/g," "),a=a.replace(/\t/g,"¨A¨B"),a=a.replace(/¨B(.+?)¨A/g,function(a,b){for(var c=b,d=4-c.length%4,e=0;d>e;e++)c+=" ";return c}),a=a.replace(/¨A/g," "),a=a.replace(/¨B/g,"")}),e.subParser("encodeAmpsAndAngles",function(a){"use strict";return a=a.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"),a=a.replace(/<(?![a-z\/?\$!])/gi,"<")}),e.subParser("encodeBackslashEscapes",function(a){"use strict";return a=a.replace(/\\(\\)/g,e.helper.escapeCharactersCallback),a=a.replace(/\\([`*_{}\[\]()>#+-.!~])/g,e.helper.escapeCharactersCallback)}),e.subParser("encodeCode",function(a){"use strict";return a=a.replace(/&/g,"&").replace(//g,">").replace(/([*_{}\[\]\\])/g,e.helper.escapeCharactersCallback)}),e.subParser("escapeSpecialCharsWithinTagAttributes",function(a){"use strict";var b=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi;return a=a.replace(b,function(a){return a.replace(/(.)<\/?code>(?=.)/g,"$1`").replace(/([\\`*_ ~=])/g,e.helper.escapeCharactersCallback)})}),e.subParser("githubCodeBlocks",function(a,b,c){"use strict";return b.ghCodeBlocks?(a=c.converter._dispatch("githubCodeBlocks.before",a,b,c),a+="¨0",a=a.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g,function(a,d,f){var g=b.omitExtraWLInCodeBlocks?"":"\n";return f=e.subParser("encodeCode")(f),f=e.subParser("detab")(f),f=f.replace(/^\n+/g,""),f=f.replace(/\n+$/g,""),f=""+f+g+"
",f=e.subParser("hashBlock")(f,b,c),"\n\n¨G"+(c.ghCodeBlocks.push({text:a,codeblock:f})-1)+"G\n\n"}),a=a.replace(/¨0/,""),c.converter._dispatch("githubCodeBlocks.after",a,b,c)):a}),e.subParser("hashBlock",function(a,b,c){"use strict";return a=a.replace(/(^\n+|\n+$)/g,""),"\n\n¨K"+(c.gHtmlBlocks.push(a)-1)+"K\n\n"}),e.subParser("hashElement",function(a,b,c){"use strict";return function(a,b){var d=b;return d=d.replace(/\n\n/g,"\n"),d=d.replace(/^\n/,""),d=d.replace(/\n+$/g,""),d="\n\n¨K"+(c.gHtmlBlocks.push(d)-1)+"K\n\n"}}),e.subParser("hashHTMLBlocks",function(a,b,c){"use strict";for(var d=["pre","div","h1","h2","h3","h4","h5","h6","blockquote","table","dl","ol","ul","script","noscript","form","fieldset","iframe","math","style","section","header","footer","nav","article","aside","address","audio","canvas","figure","hgroup","output","video","p"],f=function(a,b,d,e){var f=a;return-1!==d.search(/\bmarkdown\b/)&&(f=d+c.converter.makeHtml(b)+e),"\n\n¨K"+(c.gHtmlBlocks.push(f)-1)+"K\n\n"},g=0;g]*>",""+d[g]+">","gim");return a=a.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,e.subParser("hashElement")(a,b,c)),a=e.helper.replaceRecursiveRegExp(a,function(a){return"\n\n¨K"+(c.gHtmlBlocks.push(a)-1)+"K\n\n"},"^ {0,3}","gm"),a=a.replace(/(?:\n\n)( {0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,e.subParser("hashElement")(a,b,c))}),e.subParser("hashHTMLSpans",function(a,b,c){"use strict";for(var d=e.helper.matchRecursiveRegExp(a,"]*>","","gi"),f=0;f]*>\\s*]*>","^ {0,3}\\s*","gim")}),e.subParser("headers",function(a,b,c){"use strict";function d(a){var b,d;return d=h?a.replace(/ /g,"-").replace(/&/g,"").replace(/¨T/g,"").replace(/¨D/g,"").replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g,"").toLowerCase():a.replace(/[^\w]/g,"").toLowerCase(),c.hashLinkCounts[d]?b=d+"-"+c.hashLinkCounts[d]++:(b=d,c.hashLinkCounts[d]=1),f===!0&&(f="section"),e.helper.isString(f)?f+b:b}a=c.converter._dispatch("headers.before",a,b,c);var f=b.prefixHeaderId,g=isNaN(parseInt(b.headerLevelStart))?1:parseInt(b.headerLevelStart),h=b.ghCompatibleHeaderId,i=b.smoothLivePreview?/^(.+)[ \t]*\n={2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n=+[ \t]*\n+/gm,j=b.smoothLivePreview?/^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n-+[ \t]*\n+/gm;a=a.replace(i,function(a,f){var h=e.subParser("spanGamut")(f,b,c),i=b.noHeaderId?"":' id="'+d(f)+'"',j=g,k=""+h+"";return e.subParser("hashBlock")(k,b,c)}),a=a.replace(j,function(a,f){var h=e.subParser("spanGamut")(f,b,c),i=b.noHeaderId?"":' id="'+d(f)+'"',j=g+1,k=""+h+"";return e.subParser("hashBlock")(k,b,c)});var k=b.requireSpaceBeforeHeadingText?/^(#{1,6})[ \t]+(.+?)[ \t]*#*\n+/gm:/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm;return a=a.replace(k,function(a,f,h){var i=e.subParser("spanGamut")(h,b,c),j=b.noHeaderId?"":' id="'+d(h)+'"',k=g-1+f.length,l=""+i+"";return e.subParser("hashBlock")(l,b,c)}),a=c.converter._dispatch("headers.after",a,b,c)}),e.subParser("horizontalRule",function(a,b,c){"use strict";a=c.converter._dispatch("horizontalRule.before",a,b,c);var d=e.subParser("hashBlock")("
",b,c);return a=a.replace(/^ {0,2}( ?-){3,}[ \t]*$/gm,d),a=a.replace(/^ {0,2}( ?\*){3,}[ \t]*$/gm,d),a=a.replace(/^ {0,2}( ?_){3,}[ \t]*$/gm,d),a=c.converter._dispatch("horizontalRule.after",a,b,c)}),e.subParser("images",function(a,b,c){"use strict";function d(a,b,d,f,g,h,i,j){var k=c.gUrls,l=c.gTitles,m=c.gDimensions;if(d=d.toLowerCase(),j||(j=""),""===f||null===f){if((""===d||null===d)&&(d=b.toLowerCase().replace(/ ?\n/g," ")),f="#"+d,e.helper.isUndefined(k[d]))return a;f=k[d],e.helper.isUndefined(l[d])||(j=l[d]),e.helper.isUndefined(m[d])||(g=m[d].width,h=m[d].height)}b=b.replace(/"/g,""").replace(e.helper.regexes.asteriskAndDash,e.helper.escapeCharactersCallback),f=f.replace(e.helper.regexes.asteriskAndDash,e.helper.escapeCharactersCallback);var n='
"}a=c.converter._dispatch("images.before",a,b,c);var f=/!\[(.*?)]\s?\([ \t]*()(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g,g=/!\[([^\]]*?)] ?(?:\n *)?\[(.*?)]()()()()()/g;return a=a.replace(g,d),a=a.replace(f,d),a=c.converter._dispatch("images.after",a,b,c)}),e.subParser("italicsAndBold",function(a,b,c){"use strict";return a=c.converter._dispatch("italicsAndBold.before",a,b,c),b.literalMidWordUnderscores?(a=a.replace(/(^|\s|>|\b)__(?=\S)([\s\S]+?)__(?=\b|<|\s|$)/gm,"$1$2"),a=a.replace(/(^|\s|>|\b)_(?=\S)([\s\S]+?)_(?=\b|<|\s|$)/gm,"$1$2"),a=a.replace(/(\*\*)(?=\S)([^\r]*?\S[*]*)\1/g,"$2"),a=a.replace(/(\*)(?=\S)([^\r]*?\S)\1/g,"$2")):(a=a.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,"$2"),a=a.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"$2")),a=c.converter._dispatch("italicsAndBold.after",a,b,c)}),e.subParser("lists",function(a,b,c){"use strict";function d(a,d){c.gListLevel++,a=a.replace(/\n{2,}$/,"\n"),a+="¨0";var f=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0| {0,3}([*+-]|\d+[.])[ \t]+))/gm,g=/\n[ \t]*\n(?!¨0)/.test(a);return b.disableForced4SpacesIndentedSublists&&(f=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0|\2([*+-]|\d+[.])[ \t]+))/gm),a=a.replace(f,function(a,d,f,h,i,j,k){k=k&&""!==k.trim();var l=e.subParser("outdent")(i,b,c),m="";return j&&b.tasklists&&(m=' class="task-list-item" style="list-style-type: none;"',l=l.replace(/^[ \t]*\[(x|X| )?]/m,function(){var a='"})),l=l.replace(/^([-*+]|\d\.)[ \t]+[\S\n ]*/g,function(a){return"¨A"+a}),d||l.search(/\n{2,}/)>-1?(l=e.subParser("githubCodeBlocks")(l,b,c),l=e.subParser("blockGamut")(l,b,c)):(l=e.subParser("lists")(l,b,c),l=l.replace(/\n$/,""),l=e.subParser("hashHTMLBlocks")(l,b,c),l=l.replace(/\n\n+/g,"\n\n"),l=l.replace(/\n\n/g,"¨B"),l=g?e.subParser("paragraphs")(l,b,c):e.subParser("spanGamut")(l,b,c),l=l.replace(/¨B/g,"\n\n")),l=l.replace("¨A",""),l=""+l+"\n"}),a=a.replace(/¨0/g,""),c.gListLevel--,d&&(a=a.replace(/\s+$/,"")),a}function f(a,c,e){var f=b.disableForced4SpacesIndentedSublists?/^ ?\d+\.[ \t]/gm:/^ {0,3}\d+\.[ \t]/gm,g=b.disableForced4SpacesIndentedSublists?/^ ?[*+-][ \t]/gm:/^ {0,3}[*+-][ \t]/gm,h="ul"===c?f:g,i="";return-1!==a.search(h)?!function j(a){var b=a.search(h);-1!==b?(i+="\n<"+c+">\n"+d(a.slice(0,b),!!e)+""+c+">\n",c="ul"===c?"ol":"ul",h="ul"===c?f:g,j(a.slice(b))):i+="\n<"+c+">\n"+d(a,!!e)+""+c+">\n"}(a):i="\n<"+c+">\n"+d(a,!!e)+""+c+">\n",i}return a=c.converter._dispatch("lists.before",a,b,c),a+="¨0",a=c.gListLevel?a.replace(/^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(a,b,c){var d=c.search(/[*+-]/g)>-1?"ul":"ol";return f(b,d,!0)}):a.replace(/(\n\n|^\n?)(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(a,b,c,d){var e=d.search(/[*+-]/g)>-1?"ul":"ol";return f(c,e,!1)}),a=a.replace(/¨0/,""),a=c.converter._dispatch("lists.after",a,b,c)}),e.subParser("outdent",function(a){"use strict";return a=a.replace(/^(\t|[ ]{1,4})/gm,"¨0"),a=a.replace(/¨0/g,"")}),e.subParser("paragraphs",function(a,b,c){"use strict";a=c.converter._dispatch("paragraphs.before",a,b,c),a=a.replace(/^\n+/g,""),a=a.replace(/\n+$/g,"");for(var d=a.split(/\n{2,}/g),f=[],g=d.length,h=0;g>h;h++){var i=d[h];i.search(/¨(K|G)(\d+)\1/g)>=0?f.push(i):(i=e.subParser("spanGamut")(i,b,c),i=i.replace(/^([ \t]*)/g,""),i+="
",f.push(i))}for(g=f.length,h=0;g>h;h++){for(var j="",k=f[h],l=!1;k.search(/¨(K|G)(\d+)\1/)>=0;){var m=RegExp.$1,n=RegExp.$2;j="K"===m?c.gHtmlBlocks[n]:l?e.subParser("encodeCode")(c.ghCodeBlocks[n].text):c.ghCodeBlocks[n].codeblock,j=j.replace(/\$/g,"$$$$"),k=k.replace(/(\n\n)?¨(K|G)\d+\2(\n\n)?/,j),/^]*>\s*]*>/.test(k)&&(l=!0)}f[h]=k}return a=f.join("\n"),a=a.replace(/^\n+/g,""),a=a.replace(/\n+$/g,""),c.converter._dispatch("paragraphs.after",a,b,c)}),e.subParser("runExtension",function(a,b,c,d){"use strict";if(a.filter)b=a.filter(b,d.converter,c);else if(a.regex){var e=a.regex;!e instanceof RegExp&&(e=new RegExp(e,"g")),b=b.replace(e,a.replace)}return b}),e.subParser("spanGamut",function(a,b,c){"use strict";return a=c.converter._dispatch("spanGamut.before",a,b,c),a=e.subParser("codeSpans")(a,b,c),a=e.subParser("escapeSpecialCharsWithinTagAttributes")(a,b,c),a=e.subParser("encodeBackslashEscapes")(a,b,c),a=e.subParser("images")(a,b,c),a=e.subParser("anchors")(a,b,c),a=e.subParser("autoLinks")(a,b,c),a=e.subParser("encodeAmpsAndAngles")(a,b,c),a=e.subParser("italicsAndBold")(a,b,c),a=e.subParser("strikethrough")(a,b,c),a=b.simpleLineBreaks?a.replace(/\n/g,"
\n"):a.replace(/ +\n/g,"
\n"),a=c.converter._dispatch("spanGamut.after",a,b,c)}),e.subParser("strikethrough",function(a,b,c){"use strict";return b.strikethrough&&(a=c.converter._dispatch("strikethrough.before",a,b,c),a=a.replace(/(?:~){2}([\s\S]+?)(?:~){2}/g,"$1"),a=c.converter._dispatch("strikethrough.after",a,b,c)),a}),e.subParser("stripBlankLines",function(a){"use strict";return a.replace(/^[ \t]+$/gm,"")}),e.subParser("stripLinkDefinitions",function(a,b,c){"use strict";var d=/^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=¨0))/gm;return a+="¨0",a=a.replace(d,function(a,d,f,g,h,i,j){return d=d.toLowerCase(),c.gUrls[d]=e.subParser("encodeAmpsAndAngles")(f),i?i+j:(j&&(c.gTitles[d]=j.replace(/"|'/g,""")),b.parseImgDimensions&&g&&h&&(c.gDimensions[d]={width:g,height:h}),"")}),a=a.replace(/¨0/,"")}),e.subParser("tables",function(a,b,c){"use strict";function d(a){return/^:[ \t]*--*$/.test(a)?' style="text-align:left;"':/^--*[ \t]*:[ \t]*$/.test(a)?' style="text-align:right;"':/^:[ \t]*--*[ \t]*:$/.test(a)?' style="text-align:center;"':""}function f(a,d){var f="";return a=a.trim(),b.tableHeaderId&&(f=' id="'+a.replace(/ /g,"_").toLowerCase()+'"'),a=e.subParser("spanGamut")(a,b,c),""+a+" | \n"}function g(a,d){var f=e.subParser("spanGamut")(a,b,c);return""+f+" | \n"}function h(a,b){for(var c="\n\n\n",d=a.length,e=0;d>e;++e)c+=a[e];for(c+="
\n\n\n",e=0;e\n";for(var f=0;d>f;++f)c+=b[e][f];c+="\n"}return c+="\n
\n"}if(!b.tables)return a;var i=/^ {0,3}\|?.+\|.+\n[ \t]{0,3}\|?[ \t]*:?[ \t]*(?:-|=){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:-|=){2,}[\s\S]+?(?:\n\n|¨0)/gm;return a=c.converter._dispatch("tables.before",a,b,c),a=a.replace(i,function(a){var b,c=a.split("\n");for(b=0;b {\n let ctx = {};\n\n beforeEach(function() {\n ctx.$q = Q;\n ctx.backendSrv = {\n datasourceRequest: () => {\n return ctx.$q.when({\n status: 200\n });\n }\n };\n ctx.templateSrv = {\n replace: (str) => str,\n getAdhocFilters: () => []\n };\n\n let instanceSettings = {\n url: 'https://app.datadoghq.com/api/v1',\n jsonData: {\n api_key: '0000deadbeaf0000',\n app_key: '0000abcd0000abcd'\n }\n };\n ctx.ds = new Datasource(instanceSettings, ctx.backendSrv, ctx.templateSrv);\n });\n\n describe('When doing DataDog API request', () => {\n beforeEach(function() {\n let targets = [\n { query: 'avg:system.load.5{*}', rawQuery: true }\n ];\n\n ctx.options = {\n range: {\n from: 12340000,\n to: 12340000\n },\n targets: targets\n };\n });\n\n it('should send request with proper params', (done) => {\n let expected_params = {\n method: 'GET',\n url: 'https://app.datadoghq.com/api/v1/query',\n params: {\n api_key: '0000deadbeaf0000',\n application_key: '0000abcd0000abcd',\n from: 12340,\n to: 12340,\n query: 'avg:system.load.5{*}'\n }\n };\n let datasourceRequest = sinon.spy(ctx.ds.backendSrv, 'datasourceRequest');\n\n ctx.ds.query(ctx.options);\n expect(datasourceRequest).to.have.been.calledWith(expected_params);\n done();\n });\n });\n});\n"]}
\ No newline at end of file
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/spec/dfunc_spec.js b/loadTest/docker/grafana/plugins/datadog/dist/test/spec/dfunc_spec.js
new file mode 100644
index 0000000..ed032e3
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/spec/dfunc_spec.js
@@ -0,0 +1,84 @@
+'use strict';
+
+var _dfunc = require('../dfunc');
+
+var _dfunc2 = _interopRequireDefault(_dfunc);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+describe('when creating func instance from func names', function () {
+ it('should return func instance', function () {
+ var func = _dfunc2.default.createFuncInstance('top');
+ expect(func).to.be.ok;
+ expect(func.def.name).to.equal('top');
+ expect(func.def.params.length).to.equal(3);
+ expect(func.def.defaultParams.length).to.equal(3);
+ });
+
+ it('should return func instance from funcDef', function () {
+ var func = _dfunc2.default.createFuncInstance('top');
+ var func2 = _dfunc2.default.createFuncInstance(func.def);
+ expect(func2).to.be.ok;
+ });
+
+ it('func instance should have text representation', function () {
+ var func = _dfunc2.default.createFuncInstance('top');
+ func.params[0] = 5;
+ func.params[1] = 'mean';
+ func.params[2] = 'dir';
+ func.updateText();
+ expect(func.text).to.equal("top(5, mean, dir)");
+ });
+});
+
+describe('when rendering func instance', function () {
+
+ it('should handle single metric param', function () {
+ var func = _dfunc2.default.createFuncInstance('abs');
+ expect(func.render('a')).to.equal("abs(a)");
+ });
+
+ it('should include default params if options enable it', function () {
+ var func = _dfunc2.default.createFuncInstance('top', { withDefaultParams: true });
+ expect(func.render('a')).to.equal("top(a, 5, mean, dir)");
+ });
+
+ it('should handle int or interval params with number', function () {
+ var func = _dfunc2.default.createFuncInstance('anomalies');
+ func.params[0] = 'basic';
+ func.params[1] = '5';
+ expect(func.render('hello')).to.equal("anomalies(hello, basic, 5)");
+ });
+});
+
+describe('when requesting function categories', function () {
+ it('should return function categories', function () {
+ var catIndex = _dfunc2.default.getCategories();
+ expect(catIndex.Arithmatic.length).to.be.greaterThan(3);
+ });
+});
+
+describe('when updating func param', function () {
+ it('should update param value and update text representation', function () {
+ var func = _dfunc2.default.createFuncInstance('top', { withDefaultParams: true });
+ func.updateParam('10', 0);
+ expect(func.params[0]).to.be.equal('10');
+ func.updateText();
+ expect(func.text).to.be.equal('top(10, mean, dir)');
+ });
+
+ it('should parse numbers as float', function () {
+ var func = _dfunc2.default.createFuncInstance('outliers');
+ func.updateParam('0.5', 1);
+ expect(func.params[1]).to.be.equal('0.5');
+ });
+});
+
+describe('when updating func param with optional second parameter', function () {
+ it('should update value and text', function () {
+ var func = _dfunc2.default.createFuncInstance('fill');
+ func.updateParam('null', 0);
+ expect(func.params[0]).to.be.equal('null');
+ });
+});
+//# sourceMappingURL=dfunc_spec.js.map
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/spec/dfunc_spec.js.map b/loadTest/docker/grafana/plugins/datadog/dist/test/spec/dfunc_spec.js.map
new file mode 100644
index 0000000..39d5071
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/spec/dfunc_spec.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../../../spec/dfunc_spec.js"],"names":["describe","it","func","createFuncInstance","expect","to","be","ok","def","name","equal","params","length","defaultParams","func2","updateText","text","render","withDefaultParams","catIndex","getCategories","Arithmatic","greaterThan","updateParam"],"mappings":"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","file":"dfunc_spec.js","sourcesContent":["import dfunc from '../dfunc';\n\ndescribe('when creating func instance from func names', function() {\n it('should return func instance', function() {\n var func = dfunc.createFuncInstance('top');\n expect(func).to.be.ok;\n expect(func.def.name).to.equal('top');\n expect(func.def.params.length).to.equal(3);\n expect(func.def.defaultParams.length).to.equal(3);\n });\n\n\n it('should return func instance from funcDef', function() {\n var func = dfunc.createFuncInstance('top');\n var func2 = dfunc.createFuncInstance(func.def);\n expect(func2).to.be.ok;\n });\n\n it('func instance should have text representation', function() {\n var func = dfunc.createFuncInstance('top');\n func.params[0] = 5;\n func.params[1] = 'mean';\n func.params[2] = 'dir';\n func.updateText();\n expect(func.text).to.equal(\"top(5, mean, dir)\");\n });\n});\n\ndescribe('when rendering func instance', function() {\n\n it('should handle single metric param', function() {\n var func = dfunc.createFuncInstance('abs');\n expect(func.render('a')).to.equal(\"abs(a)\");\n });\n\n it('should include default params if options enable it', function() {\n var func = dfunc.createFuncInstance('top', { withDefaultParams: true });\n expect(func.render('a')).to.equal(\"top(a, 5, mean, dir)\");\n });\n\n it('should handle int or interval params with number', function() {\n var func = dfunc.createFuncInstance('anomalies');\n func.params[0] = 'basic';\n func.params[1] = '5';\n expect(func.render('hello')).to.equal(\"anomalies(hello, basic, 5)\");\n });\n\n});\n\ndescribe('when requesting function categories', function() {\n it('should return function categories', function() {\n var catIndex = dfunc.getCategories();\n expect(catIndex.Arithmatic.length).to.be.greaterThan(3);\n });\n});\n\ndescribe('when updating func param', function() {\n it('should update param value and update text representation', function() {\n var func = dfunc.createFuncInstance('top', { withDefaultParams: true });\n func.updateParam('10', 0);\n expect(func.params[0]).to.be.equal('10');\n func.updateText();\n expect(func.text).to.be.equal('top(10, mean, dir)');\n });\n\n it('should parse numbers as float', function() {\n var func = dfunc.createFuncInstance('outliers');\n func.updateParam('0.5', 1);\n expect(func.params[1]).to.be.equal('0.5');\n });\n});\n\ndescribe('when updating func param with optional second parameter', function() {\n it('should update value and text', function() {\n var func = dfunc.createFuncInstance('fill');\n func.updateParam('null', 0);\n expect(func.params[0]).to.be.equal('null');\n });\n});\n"]}
\ No newline at end of file
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/spec/query_ctrl_soon.js b/loadTest/docker/grafana/plugins/datadog/dist/test/spec/query_ctrl_soon.js
new file mode 100644
index 0000000..1257ea2
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/spec/query_ctrl_soon.js
@@ -0,0 +1,194 @@
+'use strict';
+
+var _module = require('../module');
+
+require('app/core/services/segment_srv');
+
+var _sinon = require('sinon');
+
+var _sinon2 = _interopRequireDefault(_sinon);
+
+var _dfunc = require('../dfunc');
+
+var _dfunc2 = _interopRequireDefault(_dfunc);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+describe('DataDogQueryCtrl', function () {
+ var ctx = {};
+
+ beforeEach(angularMocks.module('grafana.core'));
+ beforeEach(angularMocks.module('grafana.controllers'));
+ beforeEach(angularMocks.module('grafana.services'));
+
+ beforeEach(ctx.providePhase());
+ beforeEach(angularMocks.inject(function ($rootScope, $controller, $q) {
+ ctx.$q = $q;
+ ctx.scope = $rootScope.$new();
+ ctx.target = {};
+ ctx.datasource.metricFindQuery = _sinon2.default.stub().returns(ctx.$q.when([]));
+ ctx.panelCtrl = { panel: {} };
+ ctx.panelCtrl.refresh = _sinon2.default.spy();
+
+ ctx.ctrl = $controller(_module.QueryCtrl, { $scope: ctx.scope }, {
+ panelCtrl: ctx.panelCtrl,
+ datasource: ctx.datasource,
+ target: ctx.target
+ });
+ ctx.scope.$digest();
+ }));
+
+ describe('init', function () {
+ it('should validate metric key exists', function () {
+ expect(ctx.datasource.metricFindQuery.getCall(0).args[0]).to.be('test.prod.*');
+ });
+
+ it('should delete last segment if no metrics are found', function () {
+ expect(ctx.ctrl.segments[2].value).to.be('select metric');
+ });
+
+ it('should parse expression and build function model', function () {
+ expect(ctx.ctrl.functions.length).to.be(2);
+ });
+ });
+
+ describe('when adding function', function () {
+ beforeEach(function () {
+ ctx.ctrl.target.target = 'test.prod.*.count';
+ ctx.ctrl.datasource.metricFindQuery = _sinon2.default.stub().returns(ctx.$q.when([{ expandable: false }]));
+ ctx.ctrl.parseTarget();
+ ctx.ctrl.addFunction(gfunc.getFuncDef('aliasByNode'));
+ });
+
+ it('should add function with correct node number', function () {
+ expect(ctx.ctrl.functions[0].params[0]).to.be(2);
+ });
+
+ it('should update target', function () {
+ expect(ctx.ctrl.target.target).to.be('aliasByNode(test.prod.*.count, 2)');
+ });
+
+ it('should call refresh', function () {
+ expect(ctx.panelCtrl.refresh.called).to.be(true);
+ });
+ });
+
+ describe('when adding function before any metric segment', function () {
+ beforeEach(function () {
+ ctx.ctrl.target.target = '';
+ ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([{ expandable: true }]));
+ ctx.ctrl.parseTarget();
+ ctx.ctrl.addFunction(gfunc.getFuncDef('asPercent'));
+ });
+
+ it('should add function and remove select metric link', function () {
+ expect(ctx.ctrl.segments.length).to.be(0);
+ });
+ });
+
+ describe('when initalizing target without metric expression and only function', function () {
+ beforeEach(function () {
+ ctx.ctrl.target.target = 'asPercent(#A, #B)';
+ ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
+ ctx.ctrl.parseTarget();
+ ctx.scope.$digest();
+ });
+
+ it('should not add select metric segment', function () {
+ expect(ctx.ctrl.segments.length).to.be(0);
+ });
+
+ it('should add both series refs as params', function () {
+ expect(ctx.ctrl.functions[0].params.length).to.be(2);
+ });
+ });
+
+ describe('when initializing a target with single param func using variable', function () {
+ beforeEach(function () {
+ ctx.ctrl.target.target = 'movingAverage(prod.count, $var)';
+ ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
+ ctx.ctrl.parseTarget();
+ });
+
+ it('should add 2 segments', function () {
+ expect(ctx.ctrl.segments.length).to.be(2);
+ });
+
+ it('should add function param', function () {
+ expect(ctx.ctrl.functions[0].params.length).to.be(1);
+ });
+ });
+
+ describe('when initalizing target without metric expression and function with series-ref', function () {
+ beforeEach(function () {
+ ctx.ctrl.target.target = 'asPercent(metric.node.count, #A)';
+ ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
+ ctx.ctrl.parseTarget();
+ });
+
+ it('should add segments', function () {
+ expect(ctx.ctrl.segments.length).to.be(3);
+ });
+
+ it('should have correct func params', function () {
+ expect(ctx.ctrl.functions[0].params.length).to.be(1);
+ });
+ });
+
+ describe('when getting altSegments and metricFindQuery retuns empty array', function () {
+ beforeEach(function () {
+ ctx.ctrl.target.target = 'test.count';
+ ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
+ ctx.ctrl.parseTarget();
+ ctx.ctrl.getAltSegments(1).then(function (results) {
+ ctx.altSegments = results;
+ });
+ ctx.scope.$digest();
+ });
+
+ it('should have no segments', function () {
+ expect(ctx.altSegments.length).to.be(0);
+ });
+ });
+
+ describe('targetChanged', function () {
+ beforeEach(function () {
+ ctx.ctrl.datasource.metricFindQuery = _sinon2.default.stub().returns(ctx.$q.when([{ expandable: false }]));
+ ctx.ctrl.parseTarget();
+ ctx.ctrl.target.target = '';
+ ctx.ctrl.targetChanged();
+ });
+
+ it('should rebuld target after expression model', function () {
+ expect(ctx.ctrl.target.target).to.be('aliasByNode(scaleToSeconds(test.prod.*, 1), 2)');
+ });
+
+ it('should call panelCtrl.refresh', function () {
+ expect(ctx.panelCtrl.refresh.called).to.be(true);
+ });
+ });
+
+ describe('when updating targets with nested query', function () {
+ beforeEach(function () {
+ ctx.ctrl.target.target = 'scaleToSeconds(#A)';
+ ctx.ctrl.datasource.metricFindQuery = _sinon2.default.stub().returns(ctx.$q.when([{ expandable: false }]));
+ ctx.ctrl.parseTarget();
+
+ ctx.ctrl.panelCtrl.panel.targets = [{
+ target: 'nested.query.count',
+ refId: 'A'
+ }];
+
+ ctx.ctrl.updateModelTarget();
+ });
+
+ it('target should remain the same', function () {
+ expect(ctx.ctrl.target.target).to.be('scaleToSeconds(#A)');
+ });
+
+ it('targetFull should include nexted queries', function () {
+ expect(ctx.ctrl.target.targetFull).to.be('scaleToSeconds(nested.query.count)');
+ });
+ });
+});
+//# sourceMappingURL=query_ctrl_soon.js.map
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/spec/query_ctrl_soon.js.map b/loadTest/docker/grafana/plugins/datadog/dist/test/spec/query_ctrl_soon.js.map
new file mode 100644
index 0000000..2a6f73f
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/spec/query_ctrl_soon.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../../../spec/query_ctrl_soon.js"],"names":["describe","ctx","beforeEach","angularMocks","module","providePhase","inject","$rootScope","$controller","$q","scope","$new","target","datasource","metricFindQuery","stub","returns","when","panelCtrl","panel","refresh","spy","ctrl","$scope","$digest","it","expect","getCall","args","to","be","segments","value","functions","length","expandable","parseTarget","addFunction","gfunc","getFuncDef","params","called","getAltSegments","then","results","altSegments","targetChanged","targets","refId","updateModelTarget","targetFull"],"mappings":"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","file":"query_ctrl_soon.js","sourcesContent":["\nimport {QueryCtrl} from \"../module\";\nimport 'app/core/services/segment_srv';\nimport sinon from 'sinon';\n\nimport dfunc from '../dfunc';\n\ndescribe('DataDogQueryCtrl', function() {\n let ctx = {};\n\n beforeEach(angularMocks.module('grafana.core'));\n beforeEach(angularMocks.module('grafana.controllers'));\n beforeEach(angularMocks.module('grafana.services'));\n\n beforeEach(ctx.providePhase());\n beforeEach(angularMocks.inject(($rootScope, $controller, $q) => {\n ctx.$q = $q;\n ctx.scope = $rootScope.$new();\n ctx.target = {};\n ctx.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));\n ctx.panelCtrl = {panel: {}};\n ctx.panelCtrl.refresh = sinon.spy();\n\n ctx.ctrl = $controller(QueryCtrl, {$scope: ctx.scope}, {\n panelCtrl: ctx.panelCtrl,\n datasource: ctx.datasource,\n target: ctx.target\n });\n ctx.scope.$digest();\n }));\n\n describe('init', function() {\n it('should validate metric key exists', function() {\n expect(ctx.datasource.metricFindQuery.getCall(0).args[0]).to.be('test.prod.*');\n });\n\n it('should delete last segment if no metrics are found', function() {\n expect(ctx.ctrl.segments[2].value).to.be('select metric');\n });\n\n it('should parse expression and build function model', function() {\n expect(ctx.ctrl.functions.length).to.be(2);\n });\n });\n\n describe('when adding function', function() {\n beforeEach(function() {\n ctx.ctrl.target.target = 'test.prod.*.count';\n ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));\n ctx.ctrl.parseTarget();\n ctx.ctrl.addFunction(gfunc.getFuncDef('aliasByNode'));\n });\n\n it('should add function with correct node number', function() {\n expect(ctx.ctrl.functions[0].params[0]).to.be(2);\n });\n\n it('should update target', function() {\n expect(ctx.ctrl.target.target).to.be('aliasByNode(test.prod.*.count, 2)');\n });\n\n it('should call refresh', function() {\n expect(ctx.panelCtrl.refresh.called).to.be(true);\n });\n });\n\n describe('when adding function before any metric segment', function() {\n beforeEach(function() {\n ctx.ctrl.target.target = '';\n ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: true}]));\n ctx.ctrl.parseTarget();\n ctx.ctrl.addFunction(gfunc.getFuncDef('asPercent'));\n });\n\n it('should add function and remove select metric link', function() {\n expect(ctx.ctrl.segments.length).to.be(0);\n });\n });\n\n describe('when initalizing target without metric expression and only function', function() {\n beforeEach(function() {\n ctx.ctrl.target.target = 'asPercent(#A, #B)';\n ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));\n ctx.ctrl.parseTarget();\n ctx.scope.$digest();\n });\n\n it('should not add select metric segment', function() {\n expect(ctx.ctrl.segments.length).to.be(0);\n });\n\n it('should add both series refs as params', function() {\n expect(ctx.ctrl.functions[0].params.length).to.be(2);\n });\n });\n\n describe('when initializing a target with single param func using variable', function() {\n beforeEach(function() {\n ctx.ctrl.target.target = 'movingAverage(prod.count, $var)';\n ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));\n ctx.ctrl.parseTarget();\n });\n\n it('should add 2 segments', function() {\n expect(ctx.ctrl.segments.length).to.be(2);\n });\n\n it('should add function param', function() {\n expect(ctx.ctrl.functions[0].params.length).to.be(1);\n });\n });\n\n describe('when initalizing target without metric expression and function with series-ref', function() {\n beforeEach(function() {\n ctx.ctrl.target.target = 'asPercent(metric.node.count, #A)';\n ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));\n ctx.ctrl.parseTarget();\n });\n\n it('should add segments', function() {\n expect(ctx.ctrl.segments.length).to.be(3);\n });\n\n it('should have correct func params', function() {\n expect(ctx.ctrl.functions[0].params.length).to.be(1);\n });\n });\n\n describe('when getting altSegments and metricFindQuery retuns empty array', function() {\n beforeEach(function() {\n ctx.ctrl.target.target = 'test.count';\n ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));\n ctx.ctrl.parseTarget();\n ctx.ctrl.getAltSegments(1).then(function(results) {\n ctx.altSegments = results;\n });\n ctx.scope.$digest();\n });\n\n it('should have no segments', function() {\n expect(ctx.altSegments.length).to.be(0);\n });\n });\n\n describe('targetChanged', function() {\n beforeEach(function() {\n ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));\n ctx.ctrl.parseTarget();\n ctx.ctrl.target.target = '';\n ctx.ctrl.targetChanged();\n });\n\n it('should rebuld target after expression model', function() {\n expect(ctx.ctrl.target.target).to.be('aliasByNode(scaleToSeconds(test.prod.*, 1), 2)');\n });\n\n it('should call panelCtrl.refresh', function() {\n expect(ctx.panelCtrl.refresh.called).to.be(true);\n });\n });\n\n describe('when updating targets with nested query', function() {\n beforeEach(function() {\n ctx.ctrl.target.target = 'scaleToSeconds(#A)';\n ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));\n ctx.ctrl.parseTarget();\n\n ctx.ctrl.panelCtrl.panel.targets = [ {\n target: 'nested.query.count',\n refId: 'A'\n }];\n\n ctx.ctrl.updateModelTarget();\n });\n\n it('target should remain the same', function() {\n expect(ctx.ctrl.target.target).to.be('scaleToSeconds(#A)');\n });\n\n it('targetFull should include nexted queries', function() {\n expect(ctx.ctrl.target.targetFull).to.be('scaleToSeconds(nested.query.count)');\n });\n });\n});\n"]}
\ No newline at end of file
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/spec/test-main.js b/loadTest/docker/grafana/plugins/datadog/dist/test/spec/test-main.js
new file mode 100644
index 0000000..cb5285e
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/spec/test-main.js
@@ -0,0 +1,64 @@
+'use strict';
+
+var _prunk = require('prunk');
+
+var _prunk2 = _interopRequireDefault(_prunk);
+
+var _jsdom = require('jsdom');
+
+var _chai = require('chai');
+
+var _chai2 = _interopRequireDefault(_chai);
+
+var _sinonChai = require('sinon-chai');
+
+var _sinonChai2 = _interopRequireDefault(_sinonChai);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+// Mock angular module
+// JSHint options
+/* globals global: false */
+
+var angularMocks = {
+ module: function module() {
+ return {
+ directive: function directive() {}
+ };
+ }
+};
+
+var datemathMock = {
+ parse: function parse() {}
+};
+
+var momentMock = {
+ duration: function duration(num, str) {
+ return 60;
+ }
+};
+
+// Mock Grafana modules that are not available outside of the core project
+// Required for loading module.js
+_prunk2.default.mock('./css/query-editor.css!', 'no css, dude.');
+_prunk2.default.mock('app/plugins/sdk', {
+ QueryCtrl: null
+});
+_prunk2.default.mock('app/core/utils/datemath', datemathMock);
+_prunk2.default.mock('moment', momentMock);
+_prunk2.default.mock('angular', angularMocks);
+_prunk2.default.mock('jquery', 'module not found');
+
+// Setup jsdom
+// Required for loading angularjs
+global.document = (0, _jsdom.jsdom)('');
+global.window = global.document.parentWindow;
+global.navigator = window.navigator = {};
+global.Node = window.Node;
+
+// Setup Chai
+_chai2.default.should();
+_chai2.default.use(_sinonChai2.default);
+global.assert = _chai2.default.assert;
+global.expect = _chai2.default.expect;
+//# sourceMappingURL=test-main.js.map
diff --git a/loadTest/docker/grafana/plugins/datadog/dist/test/spec/test-main.js.map b/loadTest/docker/grafana/plugins/datadog/dist/test/spec/test-main.js.map
new file mode 100644
index 0000000..adcbce9
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/dist/test/spec/test-main.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../../../spec/test-main.js"],"names":["angularMocks","module","directive","datemathMock","parse","momentMock","duration","num","str","mock","QueryCtrl","global","document","window","parentWindow","navigator","Node","should","use","assert","expect"],"mappings":";;AAGA;;;;AACA;;AACA;;;;AACA;;;;;;AAEA;AARA;AACA;;AAQA,IAAIA,eAAe;AACjBC,UAAQ,kBAAW;AACjB,WAAO;AACLC,iBAAW,qBAAW,CAAE;AADnB,KAAP;AAGD;AALgB,CAAnB;;AAQA,IAAIC,eAAe;AACjBC,SAAO,iBAAW,CAAE;AADH,CAAnB;;AAIA,IAAIC,aAAa;AACfC,YAAU,kBAASC,GAAT,EAAcC,GAAd,EAAmB;AAC3B,WAAO,EAAP;AACD;AAHc,CAAjB;;AAMA;AACA;AACA,gBAAMC,IAAN,CAAW,yBAAX,EAAsC,eAAtC;AACA,gBAAMA,IAAN,CAAW,iBAAX,EAA8B;AAC5BC,aAAW;AADiB,CAA9B;AAGA,gBAAMD,IAAN,CAAW,yBAAX,EAAsCN,YAAtC;AACA,gBAAMM,IAAN,CAAW,QAAX,EAAqBJ,UAArB;AACA,gBAAMI,IAAN,CAAW,SAAX,EAAsBT,YAAtB;AACA,gBAAMS,IAAN,CAAW,QAAX,EAAqB,kBAArB;;AAEA;AACA;AACAE,OAAOC,QAAP,GAAkB,kBAAM,0DAAN,CAAlB;AACAD,OAAOE,MAAP,GAAgBF,OAAOC,QAAP,CAAgBE,YAAhC;AACAH,OAAOI,SAAP,GAAmBF,OAAOE,SAAP,GAAmB,EAAtC;AACAJ,OAAOK,IAAP,GAAcH,OAAOG,IAArB;;AAEA;AACA,eAAKC,MAAL;AACA,eAAKC,GAAL;AACAP,OAAOQ,MAAP,GAAgB,eAAKA,MAArB;AACAR,OAAOS,MAAP,GAAgB,eAAKA,MAArB","file":"test-main.js","sourcesContent":["// JSHint options\n/* globals global: false */\n\nimport prunk from 'prunk';\nimport {jsdom} from 'jsdom';\nimport chai from 'chai';\nimport sinonChai from 'sinon-chai';\n\n// Mock angular module\nvar angularMocks = {\n module: function() {\n return {\n directive: function() {}\n };\n }\n};\n\nvar datemathMock = {\n parse: function() {}\n};\n\nvar momentMock = {\n duration: function(num, str) {\n return 60;\n }\n};\n\n// Mock Grafana modules that are not available outside of the core project\n// Required for loading module.js\nprunk.mock('./css/query-editor.css!', 'no css, dude.');\nprunk.mock('app/plugins/sdk', {\n QueryCtrl: null\n});\nprunk.mock('app/core/utils/datemath', datemathMock);\nprunk.mock('moment', momentMock);\nprunk.mock('angular', angularMocks);\nprunk.mock('jquery', 'module not found');\n\n// Setup jsdom\n// Required for loading angularjs\nglobal.document = jsdom('');\nglobal.window = global.document.parentWindow;\nglobal.navigator = window.navigator = {};\nglobal.Node = window.Node;\n\n// Setup Chai\nchai.should();\nchai.use(sinonChai);\nglobal.assert = chai.assert;\nglobal.expect = chai.expect;\n"]}
\ No newline at end of file
diff --git a/loadTest/docker/grafana/plugins/datadog/docker-compose.yml b/loadTest/docker/grafana/plugins/datadog/docker-compose.yml
new file mode 100644
index 0000000..a23b4c5
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/docker-compose.yml
@@ -0,0 +1,7 @@
+grafana:
+ image: grafana/grafana:latest
+ container_name: grafana
+ ports:
+ - "3000:3000"
+ volumes:
+ - ./dist:/var/lib/grafana/plugins/datadog-datasource
diff --git a/loadTest/docker/grafana/plugins/datadog/package.json b/loadTest/docker/grafana/plugins/datadog/package.json
new file mode 100644
index 0000000..988070c
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/package.json
@@ -0,0 +1,49 @@
+{
+ "name": "grafana-datadog-datasource",
+ "private": true,
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/voidfiles/datadog-datasource.git"
+ },
+ "author": "",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/voidfiles/grafana-datasource-datagog/issues"
+ },
+ "devDependencies": {
+ "babel": "~6.5.1",
+ "babel-plugin-transform-es2015-for-of": "^6.6.0",
+ "babel-plugin-transform-es2015-modules-systemjs": "^6.5.0",
+ "babel-preset-es2015": "^6.5.0",
+ "chai": "~3.5.0",
+ "grunt": "~0.4.5",
+ "grunt-babel": "~6.0.0",
+ "grunt-contrib-clean": "~0.6.0",
+ "grunt-contrib-copy": "~0.8.2",
+ "grunt-contrib-jshint": "^1.0.0",
+ "grunt-contrib-uglify": "~0.11.0",
+ "grunt-contrib-watch": "^0.6.1",
+ "grunt-execute": "~0.2.2",
+ "grunt-jscs": "^2.8.0",
+ "grunt-mocha-test": "~0.12.7",
+ "grunt-systemjs-builder": "^0.2.5",
+ "jsdom": "~3.1.2",
+ "jshint-stylish": "^2.1.0",
+ "load-grunt-tasks": "~3.2.0",
+ "mocha": "^2.4.5",
+ "prunk": "~1.2.1",
+ "q": "~1.4.1",
+ "sinon": "~1.16.1",
+ "sinon-chai": "~2.8.0"
+ },
+ "dependencies": {
+ "lodash": "~4.0.0"
+ },
+ "homepage": "https://github.com/voidfiles/grafana-datasource-datagog/issues"
+}
diff --git a/loadTest/docker/grafana/plugins/datadog/spec/datasource_spec.js b/loadTest/docker/grafana/plugins/datadog/spec/datasource_spec.js
new file mode 100644
index 0000000..53977f4
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/spec/datasource_spec.js
@@ -0,0 +1,66 @@
+import {Datasource} from "../module";
+import Q from "q";
+import sinon from 'sinon';
+
+describe('DataDogDatasource', () => {
+ let ctx = {};
+
+ beforeEach(function() {
+ ctx.$q = Q;
+ ctx.backendSrv = {
+ datasourceRequest: () => {
+ return ctx.$q.when({
+ status: 200
+ });
+ }
+ };
+ ctx.templateSrv = {
+ replace: (str) => str,
+ getAdhocFilters: () => []
+ };
+
+ let instanceSettings = {
+ url: 'https://app.datadoghq.com/api/v1',
+ jsonData: {
+ api_key: '0000deadbeaf0000',
+ app_key: '0000abcd0000abcd'
+ }
+ };
+ ctx.ds = new Datasource(instanceSettings, ctx.backendSrv, ctx.templateSrv);
+ });
+
+ describe('When doing DataDog API request', () => {
+ beforeEach(function() {
+ let targets = [
+ { query: 'avg:system.load.5{*}', rawQuery: true }
+ ];
+
+ ctx.options = {
+ range: {
+ from: 12340000,
+ to: 12340000
+ },
+ targets: targets
+ };
+ });
+
+ it('should send request with proper params', (done) => {
+ let expected_params = {
+ method: 'GET',
+ url: 'https://app.datadoghq.com/api/v1/query',
+ params: {
+ api_key: '0000deadbeaf0000',
+ application_key: '0000abcd0000abcd',
+ from: 12340,
+ to: 12340,
+ query: 'avg:system.load.5{*}'
+ }
+ };
+ let datasourceRequest = sinon.spy(ctx.ds.backendSrv, 'datasourceRequest');
+
+ ctx.ds.query(ctx.options);
+ expect(datasourceRequest).to.have.been.calledWith(expected_params);
+ done();
+ });
+ });
+});
diff --git a/loadTest/docker/grafana/plugins/datadog/spec/dfunc_spec.js b/loadTest/docker/grafana/plugins/datadog/spec/dfunc_spec.js
new file mode 100644
index 0000000..84236c2
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/spec/dfunc_spec.js
@@ -0,0 +1,79 @@
+import dfunc from '../dfunc';
+
+describe('when creating func instance from func names', function() {
+ it('should return func instance', function() {
+ var func = dfunc.createFuncInstance('top');
+ expect(func).to.be.ok;
+ expect(func.def.name).to.equal('top');
+ expect(func.def.params.length).to.equal(3);
+ expect(func.def.defaultParams.length).to.equal(3);
+ });
+
+
+ it('should return func instance from funcDef', function() {
+ var func = dfunc.createFuncInstance('top');
+ var func2 = dfunc.createFuncInstance(func.def);
+ expect(func2).to.be.ok;
+ });
+
+ it('func instance should have text representation', function() {
+ var func = dfunc.createFuncInstance('top');
+ func.params[0] = 5;
+ func.params[1] = 'mean';
+ func.params[2] = 'dir';
+ func.updateText();
+ expect(func.text).to.equal("top(5, mean, dir)");
+ });
+});
+
+describe('when rendering func instance', function() {
+
+ it('should handle single metric param', function() {
+ var func = dfunc.createFuncInstance('abs');
+ expect(func.render('a')).to.equal("abs(a)");
+ });
+
+ it('should include default params if options enable it', function() {
+ var func = dfunc.createFuncInstance('top', { withDefaultParams: true });
+ expect(func.render('a')).to.equal("top(a, 5, mean, dir)");
+ });
+
+ it('should handle int or interval params with number', function() {
+ var func = dfunc.createFuncInstance('anomalies');
+ func.params[0] = 'basic';
+ func.params[1] = '5';
+ expect(func.render('hello')).to.equal("anomalies(hello, basic, 5)");
+ });
+
+});
+
+describe('when requesting function categories', function() {
+ it('should return function categories', function() {
+ var catIndex = dfunc.getCategories();
+ expect(catIndex.Arithmatic.length).to.be.greaterThan(3);
+ });
+});
+
+describe('when updating func param', function() {
+ it('should update param value and update text representation', function() {
+ var func = dfunc.createFuncInstance('top', { withDefaultParams: true });
+ func.updateParam('10', 0);
+ expect(func.params[0]).to.be.equal('10');
+ func.updateText();
+ expect(func.text).to.be.equal('top(10, mean, dir)');
+ });
+
+ it('should parse numbers as float', function() {
+ var func = dfunc.createFuncInstance('outliers');
+ func.updateParam('0.5', 1);
+ expect(func.params[1]).to.be.equal('0.5');
+ });
+});
+
+describe('when updating func param with optional second parameter', function() {
+ it('should update value and text', function() {
+ var func = dfunc.createFuncInstance('fill');
+ func.updateParam('null', 0);
+ expect(func.params[0]).to.be.equal('null');
+ });
+});
diff --git a/loadTest/docker/grafana/plugins/datadog/spec/query_ctrl_soon.js b/loadTest/docker/grafana/plugins/datadog/spec/query_ctrl_soon.js
new file mode 100644
index 0000000..5f80093
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/spec/query_ctrl_soon.js
@@ -0,0 +1,184 @@
+
+import {QueryCtrl} from "../module";
+import 'app/core/services/segment_srv';
+import sinon from 'sinon';
+
+import dfunc from '../dfunc';
+
+describe('DataDogQueryCtrl', function() {
+ let ctx = {};
+
+ beforeEach(angularMocks.module('grafana.core'));
+ beforeEach(angularMocks.module('grafana.controllers'));
+ beforeEach(angularMocks.module('grafana.services'));
+
+ beforeEach(ctx.providePhase());
+ beforeEach(angularMocks.inject(($rootScope, $controller, $q) => {
+ ctx.$q = $q;
+ ctx.scope = $rootScope.$new();
+ ctx.target = {};
+ ctx.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
+ ctx.panelCtrl = {panel: {}};
+ ctx.panelCtrl.refresh = sinon.spy();
+
+ ctx.ctrl = $controller(QueryCtrl, {$scope: ctx.scope}, {
+ panelCtrl: ctx.panelCtrl,
+ datasource: ctx.datasource,
+ target: ctx.target
+ });
+ ctx.scope.$digest();
+ }));
+
+ describe('init', function() {
+ it('should validate metric key exists', function() {
+ expect(ctx.datasource.metricFindQuery.getCall(0).args[0]).to.be('test.prod.*');
+ });
+
+ it('should delete last segment if no metrics are found', function() {
+ expect(ctx.ctrl.segments[2].value).to.be('select metric');
+ });
+
+ it('should parse expression and build function model', function() {
+ expect(ctx.ctrl.functions.length).to.be(2);
+ });
+ });
+
+ describe('when adding function', function() {
+ beforeEach(function() {
+ ctx.ctrl.target.target = 'test.prod.*.count';
+ ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));
+ ctx.ctrl.parseTarget();
+ ctx.ctrl.addFunction(gfunc.getFuncDef('aliasByNode'));
+ });
+
+ it('should add function with correct node number', function() {
+ expect(ctx.ctrl.functions[0].params[0]).to.be(2);
+ });
+
+ it('should update target', function() {
+ expect(ctx.ctrl.target.target).to.be('aliasByNode(test.prod.*.count, 2)');
+ });
+
+ it('should call refresh', function() {
+ expect(ctx.panelCtrl.refresh.called).to.be(true);
+ });
+ });
+
+ describe('when adding function before any metric segment', function() {
+ beforeEach(function() {
+ ctx.ctrl.target.target = '';
+ ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: true}]));
+ ctx.ctrl.parseTarget();
+ ctx.ctrl.addFunction(gfunc.getFuncDef('asPercent'));
+ });
+
+ it('should add function and remove select metric link', function() {
+ expect(ctx.ctrl.segments.length).to.be(0);
+ });
+ });
+
+ describe('when initalizing target without metric expression and only function', function() {
+ beforeEach(function() {
+ ctx.ctrl.target.target = 'asPercent(#A, #B)';
+ ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
+ ctx.ctrl.parseTarget();
+ ctx.scope.$digest();
+ });
+
+ it('should not add select metric segment', function() {
+ expect(ctx.ctrl.segments.length).to.be(0);
+ });
+
+ it('should add both series refs as params', function() {
+ expect(ctx.ctrl.functions[0].params.length).to.be(2);
+ });
+ });
+
+ describe('when initializing a target with single param func using variable', function() {
+ beforeEach(function() {
+ ctx.ctrl.target.target = 'movingAverage(prod.count, $var)';
+ ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
+ ctx.ctrl.parseTarget();
+ });
+
+ it('should add 2 segments', function() {
+ expect(ctx.ctrl.segments.length).to.be(2);
+ });
+
+ it('should add function param', function() {
+ expect(ctx.ctrl.functions[0].params.length).to.be(1);
+ });
+ });
+
+ describe('when initalizing target without metric expression and function with series-ref', function() {
+ beforeEach(function() {
+ ctx.ctrl.target.target = 'asPercent(metric.node.count, #A)';
+ ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
+ ctx.ctrl.parseTarget();
+ });
+
+ it('should add segments', function() {
+ expect(ctx.ctrl.segments.length).to.be(3);
+ });
+
+ it('should have correct func params', function() {
+ expect(ctx.ctrl.functions[0].params.length).to.be(1);
+ });
+ });
+
+ describe('when getting altSegments and metricFindQuery retuns empty array', function() {
+ beforeEach(function() {
+ ctx.ctrl.target.target = 'test.count';
+ ctx.ctrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
+ ctx.ctrl.parseTarget();
+ ctx.ctrl.getAltSegments(1).then(function(results) {
+ ctx.altSegments = results;
+ });
+ ctx.scope.$digest();
+ });
+
+ it('should have no segments', function() {
+ expect(ctx.altSegments.length).to.be(0);
+ });
+ });
+
+ describe('targetChanged', function() {
+ beforeEach(function() {
+ ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));
+ ctx.ctrl.parseTarget();
+ ctx.ctrl.target.target = '';
+ ctx.ctrl.targetChanged();
+ });
+
+ it('should rebuld target after expression model', function() {
+ expect(ctx.ctrl.target.target).to.be('aliasByNode(scaleToSeconds(test.prod.*, 1), 2)');
+ });
+
+ it('should call panelCtrl.refresh', function() {
+ expect(ctx.panelCtrl.refresh.called).to.be(true);
+ });
+ });
+
+ describe('when updating targets with nested query', function() {
+ beforeEach(function() {
+ ctx.ctrl.target.target = 'scaleToSeconds(#A)';
+ ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));
+ ctx.ctrl.parseTarget();
+
+ ctx.ctrl.panelCtrl.panel.targets = [ {
+ target: 'nested.query.count',
+ refId: 'A'
+ }];
+
+ ctx.ctrl.updateModelTarget();
+ });
+
+ it('target should remain the same', function() {
+ expect(ctx.ctrl.target.target).to.be('scaleToSeconds(#A)');
+ });
+
+ it('targetFull should include nexted queries', function() {
+ expect(ctx.ctrl.target.targetFull).to.be('scaleToSeconds(nested.query.count)');
+ });
+ });
+});
diff --git a/loadTest/docker/grafana/plugins/datadog/spec/test-main.js b/loadTest/docker/grafana/plugins/datadog/spec/test-main.js
new file mode 100644
index 0000000..6270f32
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/spec/test-main.js
@@ -0,0 +1,50 @@
+// JSHint options
+/* globals global: false */
+
+import prunk from 'prunk';
+import {jsdom} from 'jsdom';
+import chai from 'chai';
+import sinonChai from 'sinon-chai';
+
+// Mock angular module
+var angularMocks = {
+ module: function() {
+ return {
+ directive: function() {}
+ };
+ }
+};
+
+var datemathMock = {
+ parse: function() {}
+};
+
+var momentMock = {
+ duration: function(num, str) {
+ return 60;
+ }
+};
+
+// Mock Grafana modules that are not available outside of the core project
+// Required for loading module.js
+prunk.mock('./css/query-editor.css!', 'no css, dude.');
+prunk.mock('app/plugins/sdk', {
+ QueryCtrl: null
+});
+prunk.mock('app/core/utils/datemath', datemathMock);
+prunk.mock('moment', momentMock);
+prunk.mock('angular', angularMocks);
+prunk.mock('jquery', 'module not found');
+
+// Setup jsdom
+// Required for loading angularjs
+global.document = jsdom('');
+global.window = global.document.parentWindow;
+global.navigator = window.navigator = {};
+global.Node = window.Node;
+
+// Setup Chai
+chai.should();
+chai.use(sinonChai);
+global.assert = chai.assert;
+global.expect = chai.expect;
diff --git a/loadTest/docker/grafana/plugins/datadog/src/add_datadog_func.js b/loadTest/docker/grafana/plugins/datadog/src/add_datadog_func.js
new file mode 100644
index 0000000..13938c4
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/src/add_datadog_func.js
@@ -0,0 +1,103 @@
+import angular from 'angular';
+import _ from 'lodash';
+import $ from 'jquery';
+import dfunc from './dfunc';
+
+angular
+ .module('grafana.directives')
+ .directive('datadogAddFunc', function($compile) {
+ var inputTemplate = '';
+
+ var buttonTemplate = '' +
+ '';
+
+ return {
+ link: function($scope, elem) {
+ var categories = dfunc.getCategories();
+ var allFunctions = getAllFunctionNames(categories);
+ var ctrl = $scope.ctrl;
+
+ $scope.functionMenu = createFunctionDropDownMenu(categories);
+
+ var $input = $(inputTemplate);
+ var $button = $(buttonTemplate);
+ $input.appendTo(elem);
+ $button.appendTo(elem);
+
+ $input.attr('data-provide', 'typeahead');
+ $input.typeahead({
+ source: allFunctions,
+ minLength: 1,
+ items: 10,
+ updater: function (value) {
+ var funcDef = dfunc.getFuncDef(value);
+ if (!funcDef) {
+ // try find close match
+ value = value.toLowerCase();
+ funcDef = _.find(allFunctions, function(funcName) {
+ return funcName.toLowerCase().indexOf(value) === 0;
+ });
+
+ if (!funcDef) { return; }
+ }
+
+ $scope.$apply(function() {
+ ctrl.addFunction(funcDef);
+ });
+
+ $input.trigger('blur');
+ return '';
+ }
+ });
+
+ $button.click(function() {
+ $button.hide();
+ $input.show();
+ $input.focus();
+ });
+
+ $input.keyup(function() {
+ elem.toggleClass('open', $input.val() === '');
+ });
+
+ $input.blur(function() {
+ // clicking the function dropdown menu wont
+ // work if you remove class at once
+ setTimeout(function() {
+ $input.val('');
+ $input.hide();
+ $button.show();
+ elem.removeClass('open');
+ }, 200);
+ });
+
+ $compile(elem.contents())($scope);
+ }
+ };
+ });
+
+function getAllFunctionNames(categories) {
+ return _.reduce(categories, function(list, category) {
+ _.each(category, function(func) {
+ list.push(func.name);
+ });
+ return list;
+ }, []);
+}
+
+function createFunctionDropDownMenu(categories) {
+ return _.map(categories, function(list, category) {
+ return {
+ text: category,
+ submenu: _.map(list, function(value) {
+ return {
+ text: value.name,
+ click: "ctrl.addFunction('" + value.name + "')",
+ };
+ })
+ };
+ });
+}
diff --git a/loadTest/docker/grafana/plugins/datadog/src/datasource.js b/loadTest/docker/grafana/plugins/datadog/src/datasource.js
new file mode 100644
index 0000000..483389e
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/src/datasource.js
@@ -0,0 +1,374 @@
+import _ from 'lodash';
+import showdown from './showdown.min.js';
+import * as queryBuilder from './query_builder';
+
+export class DataDogDatasource {
+
+ constructor (instanceSettings, backendSrv, templateSrv) {
+ this.type = instanceSettings.type;
+ this.url = instanceSettings.url;
+ this.name = instanceSettings.name;
+ this.api_key = instanceSettings.jsonData.api_key;
+ this.application_key = instanceSettings.jsonData.app_key;
+ this.supportMetrics = true;
+ this.backendSrv = backendSrv;
+ this.templateSrv = templateSrv;
+ this._cached_metrics = false;
+ }
+
+ // Function to check Datasource health
+ testDatasource() {
+ return this.invokeDataDogApiRequest('/downtime')
+ .then(() => {
+ return {
+ status: "success",
+ title: "Success",
+ message: "Data source is working"
+ };
+ })
+ .catch(error => {
+ var message = "Connection error";
+ if (error && error.message) {
+ message = error.message;
+ }
+
+ return {
+ status: "error",
+ title: "Error",
+ message: message
+ };
+ });
+ }
+
+ tagFindQuery() {
+ return this.getTagsFromCache()
+ .then(tags => {
+ return _.map(tags, (hosts, tag) => {
+ return {
+ text: tag,
+ value: tag,
+ };
+ });
+ });
+ }
+
+ metricFindQuery(query) {
+ if (query === 'tag') {
+ return this.tagFindQuery();
+ }
+
+ if (this._cached_metrics) {
+ return Promise.resolve(this._cached_metrics);
+ }
+
+ if (this.fetching) {
+ return this.fetching;
+ }
+
+ var d = new Date();
+ d.setDate(d.getDate() - 1);
+ var from = Math.floor(d.getTime() / 1000);
+
+ this.fetching = this.getMetrics(from).then(metrics => {
+ this._cached_metrics = _.map(metrics, metric => {
+ return {
+ text: metric,
+ value: metric,
+ };
+ });
+
+ return this._cached_metrics;
+ });
+
+ return this.fetching;
+ }
+
+ getTagKeys() {
+ return this.getTagsFromCache()
+ .then(tagsHosts => {
+ let tags = Object.keys(tagsHosts);
+ let kv = mapTagsToKVPairs(tags);
+ let grafanaTags = Object.keys(kv);
+
+ return grafanaTags.map(tag => {
+ return {
+ text: tag,
+ value: tag,
+ };
+ });
+ });
+ }
+
+ getTagValues(options) {
+ return this.getTagsFromCache()
+ .then(tagsHosts => {
+ let tags = Object.keys(tagsHosts);
+ let kv = mapTagsToKVPairs(tags);
+ let grafanaValues = kv[options.key];
+ return grafanaValues.map(val => {
+ return {
+ text: val,
+ value: val,
+ };
+ });
+ });
+ }
+
+ query(options) {
+ var from = Math.floor(options.range.from.valueOf() / 1000);
+ var to = Math.floor(options.range.to.valueOf() / 1000);
+
+ var targets = options.targets.filter(function (t) { return !t.hide; });
+
+ if (targets.length <= 0) {
+ return Promise.resolve({data: []});
+ }
+
+ // add global adhoc filters
+ let adhocFilters = this.templateSrv.getAdhocFilters(this.name);
+
+ var queries = _.map(targets, target => {
+ let query;
+ if (target.rawQuery) {
+ query = target.query;
+ } else {
+ query = queryBuilder.buildQuery(target, adhocFilters);
+ }
+ return query;
+ });
+
+ var queryString = queries.join(',');
+ queryString = this.templateSrv.replace(queryString, options.scopedVars, dataDogTagFormat);
+
+ var params = {
+ from: from,
+ to: to,
+ query: queryString,
+ };
+
+ return this.invokeDataDogApiRequest('/query', params)
+ .then(result => {
+ var dataResponse = _.map(result.series, (series, i) => {
+ var target = targets[i];
+ return {
+ 'target': target.alias || series.expression,
+ 'datapoints': _.map(series.pointlist, point => {
+ return [point[1], point[0]];
+ })
+ };
+ });
+
+ return {data: dataResponse};
+ });
+ }
+
+ annotationQuery(options) {
+ let timeFrom = Math.floor(options.range.from.valueOf() / 1000);
+ let timeTo = Math.floor(options.range.to.valueOf() / 1000);
+ let {priority, sources, tags} = options.annotation;
+
+ return this.getEventStream(timeFrom, timeTo, priority, sources, tags)
+ .then(eventStreams => {
+ let eventAnnotations = eventStreams.map(eventStream => {
+ let allEvents = eventStream.children;
+ let filteredEvents = _.filter(allEvents, event => {
+ return event.alert_type !== 'success';
+ });
+
+ return _.map(filteredEvents, event => {
+ let renderedText = eventStream.text;
+ if (isDataDogMarkdown(eventStream.text)) {
+ renderedText = convertDataDogMdToHtml(eventStream.text);
+ }
+
+ return {
+ annotation: options.annotation,
+ time: event.date_happened * 1000,
+ title: eventStream.title,
+ text: renderedText,
+ tags: eventStream.tags
+ };
+ });
+ });
+
+ return _.flatten(eventAnnotations);
+ });
+ }
+
+ getHosts() {
+ return this.searchEntities('hosts');
+ }
+
+ // entity should be 'hosts' or 'metrics'
+ // http://docs.datadoghq.com/api/?lang=console#search
+ searchEntities(entity) {
+ let params = {q: ''};
+ if (entity) {
+ params.q = `${entity}:`;
+ }
+
+ return this.invokeDataDogApiRequest('/search', params)
+ .then(result => {
+ if (result && result.results) {
+ return result.results[entity];
+ }
+ });
+ }
+
+ getMetrics(timeFrom) {
+ let params = {};
+
+ if (timeFrom) {
+ params.from = timeFrom;
+ }
+
+ return this.invokeDataDogApiRequest('/metrics', params)
+ .then(result => {
+ if (result.metrics) {
+ return result.metrics;
+ } else {
+ return [];
+ }
+ });
+ }
+
+ getTagsHosts() {
+ return this.invokeDataDogApiRequest('/tags/hosts')
+ .then(result => {
+ if (result && result.tags) {
+ return result.tags;
+ }
+ });
+ }
+
+ getTagsFromCache() {
+ let getTags;
+ if (this._cached_tags && this._cached_tags.length) {
+ getTags = Promise.resolve(this._cached_tags);
+ } else {
+ getTags = this.getTagsHosts().then(tags => {
+ this._cached_tags = tags;
+ return tags;
+ });
+ }
+
+ return getTags;
+ }
+
+ getEventStream(timeFrom, timeTo, priority, sources, tags) {
+ let params = {
+ start: timeFrom,
+ end: timeTo
+ };
+ if (priority) {
+ params.priority = priority;
+ }
+ if (sources) {
+ params.sources = sources;
+ }
+ if (tags) {
+ params.tags = tags;
+ }
+
+ return this.invokeDataDogApiRequest('/events', params)
+ .then(result => {
+ if (result.events) {
+ return result.events;
+ } else {
+ return [];
+ }
+ });
+ }
+
+ invokeDataDogApiRequest(url, params = {}) {
+ // Set auth params
+ params.api_key = this.api_key;
+ params.application_key = this.application_key;
+
+ return this.backendSrv.datasourceRequest({
+ method: 'GET',
+ url: this.url + url,
+ params: params
+ })
+ .then(response => {
+ if (response.data) {
+ return response.data;
+ } else {
+ throw {message: 'DataDog API request error'};
+ }
+ })
+ .catch(error => {
+ var message = 'DataDog API request error';
+ if (error.statusText) {
+ message = error.status + ' ' + error.statusText;
+ throw {message: message};
+ } else if (error.err.statusText) {
+ throw {message: error.err.statusText};
+ } else {
+ throw {message: message};
+ }
+ });
+ }
+}
+
+/*
+ * Convert tags to key-value pairs
+ * [region:east, region:nw] => {region: [east, nw]}
+ */
+function mapTagsToKVPairs(tags) {
+ let kv_tags = _.filter(tags, tag => {
+ return (tag.indexOf(':') !== -1);
+ });
+
+ let kv_pairs = kv_tags.map(tag => {
+ return tag.split(':', 2); // Limit to 2
+ });
+
+ let kv_object = {};
+ kv_pairs.forEach(pair => {
+ let key = pair[0];
+ let val = pair[1];
+
+ if (kv_object[key]) {
+ kv_object[key].push(val);
+ } else {
+ kv_object[key] = [val];
+ }
+ });
+
+ return kv_object;
+}
+
+/*
+ * Convert DataDog event text from markdown to pure HTML
+ * http://docs.datadoghq.com/guides/markdown/
+ */
+function convertDataDogMdToHtml(str) {
+ const MD_START = '%%%\n';
+ const MD_END = '\n%%%';
+
+ let md_start_index = str.indexOf(MD_START) + MD_START.length;
+ let md_end_index = str.indexOf(MD_END);
+ let md = str.substring(md_start_index, md_end_index);
+ md = removeImagesFromDataDogMarkdown(md);
+
+ let converter = new showdown.Converter();
+ return converter.makeHtml(md);
+}
+
+function isDataDogMarkdown(str) {
+ const MD_START = '%%%\n';
+ return str.indexOf(MD_START) >= 0;
+}
+
+function removeImagesFromDataDogMarkdown(str) {
+ let dataDogImagePattern = /\[{0,1}\!\[.*\]\(https{0,1}\:\/\/p\.datadoghq\.com\/snapshot.+\)/g;
+ return str.replace(dataDogImagePattern, '');
+}
+
+function dataDogTagFormat(value) {
+ if (_.isArray(value)) {
+ return value.join(',');
+ }
+ return value;
+}
diff --git a/loadTest/docker/grafana/plugins/datadog/src/dfunc.js b/loadTest/docker/grafana/plugins/datadog/src/dfunc.js
new file mode 100644
index 0000000..ec7b5c5
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/src/dfunc.js
@@ -0,0 +1,316 @@
+import _ from 'lodash';
+
+var index = [];
+var categories = {
+ Arithmatic: [],
+ Interpolation: [],
+ Timeshift: [],
+ Rate: [],
+ Smoothing: [],
+ Rollup: [],
+ Rank: [],
+ Count: [],
+ Regression: [],
+ Algorithms: []
+};
+
+function addFuncDef(funcDef) {
+ funcDef.params = funcDef.params || [];
+ funcDef.defaultParams = funcDef.defaultParams || [];
+ funcDef.append = funcDef.append || false;
+ if (funcDef.category) {
+ funcDef.category.push(funcDef);
+ }
+ index[funcDef.name] = funcDef;
+ index[funcDef.name] = funcDef;
+}
+
+addFuncDef({
+ name: 'abs',
+ category: categories.Arithmatic,
+});
+
+addFuncDef({
+ name: 'log2',
+ category: categories.Arithmatic,
+});
+
+addFuncDef({
+ name: 'cumsum',
+ category: categories.Arithmatic,
+});
+
+addFuncDef({
+ name: 'integral',
+ category: categories.Arithmatic,
+});
+
+addFuncDef({
+ name: 'fill',
+ category: categories.Interpolation,
+ params: [
+ { name: "fill", type: 'string', options: ['null', 'zero', 'linear', 'last'] },
+ { name: "limit", type: 'int', optional: true},
+ ],
+ defaultParams: ['zero'],
+ append: true,
+});
+
+addFuncDef({
+ name: 'hour_before',
+ category: categories.Timeshift,
+});
+
+addFuncDef({
+ name: 'day_before',
+ category: categories.Timeshift,
+});
+
+addFuncDef({
+ name: 'week_before',
+ category: categories.Timeshift,
+});
+
+addFuncDef({
+ name: 'month_before',
+ category: categories.Timeshift,
+});
+
+addFuncDef({
+ name: 'per_second',
+ category: categories.Rate,
+});
+
+addFuncDef({
+ name: 'per_minute',
+ category: categories.Rate,
+});
+
+addFuncDef({
+ name: 'per_hour',
+ category: categories.Rate,
+});
+
+addFuncDef({
+ name: 'dt',
+ category: categories.Rate,
+});
+
+addFuncDef({
+ name: 'diff',
+ category: categories.Rate,
+});
+
+addFuncDef({
+ name: 'derivative',
+ category: categories.Rate,
+});
+
+addFuncDef({
+ name: 'ewma_3',
+ category: categories.Smoothing,
+});
+
+addFuncDef({
+ name: 'ewma_5',
+ category: categories.Smoothing,
+});
+
+addFuncDef({
+ name: 'ewma_10',
+ category: categories.Smoothing,
+});
+
+addFuncDef({
+ name: 'ewma_20',
+ category: categories.Smoothing,
+});
+
+addFuncDef({
+ name: 'median_3',
+ category: categories.Smoothing,
+});
+
+addFuncDef({
+ name: 'median_5',
+ category: categories.Smoothing,
+});
+
+addFuncDef({
+ name: 'median_7',
+ category: categories.Smoothing,
+});
+
+addFuncDef({
+ name: 'median_9',
+ category: categories.Smoothing,
+});
+
+addFuncDef({
+ name: 'rollup',
+ category: categories.Rollup,
+ params: [
+ { name: "rollup", type: 'string', options: ['sum', 'avg', 'min', 'max'] },
+ { name: "period", type: 'int'},
+ ],
+ defaultParams: ['sum', 600],
+ append: true
+});
+
+addFuncDef({
+ name: 'count_nonzero',
+ category: categories.Count,
+});
+
+addFuncDef({
+ name: 'count_not_null',
+ category: categories.Count,
+});
+
+addFuncDef({
+ name: 'top',
+ category: categories.Rank,
+ params: [
+ { name: "top", type: 'int', options: [5, 10, 15]},
+ { name: "aggr", type: 'string', options: ['mean', 'min', 'max', 'area', 'l2norm', 'last']},
+ { name: "dir", type: 'string', options: ['asc', 'desc']},
+ ],
+ defaultParams: [5, 'mean', 'dir'],
+});
+
+addFuncDef({
+ name: 'robust_trend',
+ category: categories.Regression,
+});
+
+addFuncDef({
+ name: 'piecewise_constant',
+ category: categories.Regression,
+});
+
+addFuncDef({
+ name: 'anomalies',
+ category: categories.Algorithms,
+ params: [
+ { name: "anomalies", type: 'string', options: ['basic', 'agile', 'robust', 'adaptive']},
+ { name: "bonds", type: 'int', options: [1, 2, 3, 4, 5, 6]},
+ ],
+ defaultParams: ['basic', 2],
+});
+
+addFuncDef({
+ name: 'anomalies',
+ category: categories.Algorithms,
+ params: [
+ { name: "anomalies", type: 'string', options: ['basic', 'agile', 'robust', 'adaptive']},
+ { name: "bonds", type: 'int', options: [1, 2, 3, 4, 5, 6]},
+ ],
+ defaultParams: ['basic', 2],
+});
+
+addFuncDef({
+ name: 'outliers',
+ category: categories.Algorithms,
+ params: [
+ { name: "outliers", type: 'string', options: ['DBSCAN', 'MAD', 'scaledDBSCAN', 'scaledMAD']},
+ { name: "tolerance", type: 'int', options: [
+ 0.33,
+ 0.5,
+ 1,
+ 1.5,
+ 2.0,
+ 2.5,
+ 3.0,
+ 3.5,
+ 4.0,
+ 4.5,
+ 5.0]},
+ ],
+ defaultParams: ['basic', 2],
+});
+
+_.each(categories, function(funcList, catName) {
+ categories[catName] = _.sortBy(funcList, 'name');
+});
+
+function FuncInstance(funcDef, options) {
+ this.def = funcDef;
+ this.params = [];
+
+ if (options && options.withDefaultParams) {
+ this.params = funcDef.defaultParams.slice(0);
+ }
+
+ this.updateText();
+}
+
+FuncInstance.prototype.render = function(metricExp) {
+ var str = this.def.name + '(';
+ var parameters = _.map(this.params, function(value) {
+ return value;
+ });
+ if (metricExp && !this.def.append) {
+ parameters.unshift(metricExp);
+ }
+
+ var funcRender = str + parameters.join(', ') + ')';
+ console.log(funcRender, this.def.append);
+ if (this.def.append) {
+ return metricExp + '.' + funcRender;
+ } else {
+ return funcRender;
+ }
+};
+
+FuncInstance.prototype.updateParam = function(strValue, index) {
+ // handle optional parameters
+ // if string contains ',' and next param is optional, split and update both
+
+ if (strValue === '' && this.def.params[index].optional) {
+ this.params.splice(index, 1);
+ }
+ else {
+ this.params[index] = strValue;
+ }
+
+ this.updateText();
+};
+
+FuncInstance.prototype.updateText = function () {
+ if (this.params.length === 0) {
+ this.text = this.def.name + '()';
+ return;
+ }
+
+ var text = this.def.name + '(';
+ text += this.params.join(', ');
+ text += ')';
+ this.text = text;
+};
+
+var createFuncInstance = function(funcDef, options) {
+ if (_.isString(funcDef)) {
+ if (!index[funcDef]) {
+ throw { message: 'Method not found ' + name };
+ }
+ funcDef = index[funcDef];
+ }
+ return new FuncInstance(funcDef, options);
+};
+
+var getFuncDef = function(name) {
+ return index[name];
+};
+
+var getCategories = function() {
+ return categories;
+};
+
+var foo;
+
+export default foo = {
+ createFuncInstance: createFuncInstance,
+ getFuncDef: getFuncDef,
+ getCategories: getCategories,
+};
+
diff --git a/loadTest/docker/grafana/plugins/datadog/src/func_editor.js b/loadTest/docker/grafana/plugins/datadog/src/func_editor.js
new file mode 100644
index 0000000..5989505
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/src/func_editor.js
@@ -0,0 +1,243 @@
+import angular from 'angular';
+import _ from 'lodash';
+import $ from 'jquery';
+
+'use strict';
+
+angular
+ .module('grafana.directives')
+ .directive('datadogFuncEditor', function($compile, templateSrv) {
+
+ var funcSpanTemplate = '{{func.def.name}}(';
+ var paramTemplate = '';
+
+ var funcControlsTemplate =
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
';
+
+ return {
+ restrict: 'A',
+ link: function postLink($scope, elem) {
+ var $funcLink = $(funcSpanTemplate);
+ var $funcControls = $(funcControlsTemplate);
+ var ctrl = $scope.ctrl;
+ var func = $scope.func;
+ var funcDef = func.def;
+ var scheduledRelink = false;
+ var paramCountAtLink = 0;
+
+ function clickFuncParam(paramIndex) {
+ /*jshint validthis:true */
+
+ var $link = $(this);
+ var $input = $link.next();
+
+ $input.val(func.params[paramIndex]);
+ $input.css('width', ($link.width() + 16) + 'px');
+
+ $link.hide();
+ $input.show();
+ $input.focus();
+ $input.select();
+
+ var typeahead = $input.data('typeahead');
+ if (typeahead) {
+ $input.val('');
+ typeahead.lookup();
+ }
+ }
+
+ function scheduledRelinkIfNeeded() {
+ if (paramCountAtLink === func.params.length) {
+ return;
+ }
+
+ if (!scheduledRelink) {
+ scheduledRelink = true;
+ setTimeout(function() {
+ relink();
+ scheduledRelink = false;
+ }, 200);
+ }
+ }
+
+ function inputBlur(paramIndex) {
+ /*jshint validthis:true */
+ var $input = $(this);
+ var $link = $input.prev();
+ var newValue = $input.val();
+
+ if (newValue !== '' || func.def.params[paramIndex].optional) {
+ $link.html(templateSrv.highlightVariablesAsHtml(newValue));
+
+ func.updateParam($input.val(), paramIndex);
+ scheduledRelinkIfNeeded();
+
+ $scope.$apply(function() {
+ ctrl.targetChanged();
+ });
+
+ $input.hide();
+ $link.show();
+ }
+ }
+
+ function inputKeyPress(paramIndex, e) {
+ /*jshint validthis:true */
+ if(e.which === 13) {
+ inputBlur.call(this, paramIndex);
+ }
+ }
+
+ function inputKeyDown() {
+ /*jshint validthis:true */
+ this.style.width = (3 + this.value.length) * 8 + 'px';
+ }
+
+ function addTypeahead($input, paramIndex) {
+ $input.attr('data-provide', 'typeahead');
+
+ var options = funcDef.params[paramIndex].options;
+ if (funcDef.params[paramIndex].type === 'int') {
+ options = _.map(options, function(val) { return val.toString(); });
+ }
+
+ $input.typeahead({
+ source: options,
+ minLength: 0,
+ items: 20,
+ updater: function (value) {
+ setTimeout(function() {
+ inputBlur.call($input[0], paramIndex);
+ }, 0);
+ return value;
+ }
+ });
+
+ var typeahead = $input.data('typeahead');
+ typeahead.lookup = function () {
+ this.query = this.$element.val() || '';
+ return this.process(this.source);
+ };
+ }
+
+ function toggleFuncControls() {
+ var targetDiv = elem.closest('.tight-form');
+
+ if (elem.hasClass('show-function-controls')) {
+ elem.removeClass('show-function-controls');
+ targetDiv.removeClass('has-open-function');
+ $funcControls.hide();
+ return;
+ }
+
+ elem.addClass('show-function-controls');
+ targetDiv.addClass('has-open-function');
+
+ $funcControls.show();
+ }
+
+ function addElementsAndCompile() {
+ $funcControls.appendTo(elem);
+ $funcLink.appendTo(elem);
+
+ _.each(funcDef.params, function(param, index) {
+ if (param.optional && func.params.length <= index) {
+ return;
+ }
+
+ if (index > 0) {
+ $(', ').appendTo(elem);
+ }
+
+ var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]);
+ var $paramLink = $('' + paramValue + '');
+ var $input = $(paramTemplate);
+
+ paramCountAtLink++;
+
+ $paramLink.appendTo(elem);
+ $input.appendTo(elem);
+
+ $input.blur(_.partial(inputBlur, index));
+ $input.keyup(inputKeyDown);
+ $input.keypress(_.partial(inputKeyPress, index));
+ $paramLink.click(_.partial(clickFuncParam, index));
+
+ if (funcDef.params[index].options) {
+ addTypeahead($input, index);
+ }
+
+ });
+
+ $(')').appendTo(elem);
+
+ $compile(elem.contents())($scope);
+ }
+
+ function ifJustAddedFocusFistParam() {
+ if ($scope.func.added) {
+ $scope.func.added = false;
+ setTimeout(function() {
+ elem.find('.datadog-func-param-link').first().click();
+ }, 10);
+ }
+ }
+
+ function registerFuncControlsToggle() {
+ $funcLink.click(toggleFuncControls);
+ }
+
+ function registerFuncControlsActions() {
+ $funcControls.click(function(e) {
+ var $target = $(e.target);
+ if ($target.hasClass('fa-remove')) {
+ toggleFuncControls();
+ $scope.$apply(function() {
+ ctrl.removeFunction($scope.func);
+ });
+ return;
+ }
+
+ if ($target.hasClass('fa-arrow-left')) {
+ $scope.$apply(function() {
+ _.move(ctrl.functions, $scope.$index, $scope.$index - 1);
+ ctrl.targetChanged();
+ });
+ return;
+ }
+
+ if ($target.hasClass('fa-arrow-right')) {
+ $scope.$apply(function() {
+ _.move(ctrl.functions, $scope.$index, $scope.$index + 1);
+ ctrl.targetChanged();
+ });
+ return;
+ }
+
+ if ($target.hasClass('fa-question-circle')) {
+ window.open("http://docs.datadoghq.com/graphing/#apply-more-advanced-functions", '_blank');
+ return;
+ }
+ });
+ }
+
+ function relink() {
+ elem.children().remove();
+
+ addElementsAndCompile();
+ ifJustAddedFocusFistParam();
+ registerFuncControlsToggle();
+ registerFuncControlsActions();
+ }
+
+ relink();
+ }
+ };
+
+ });
diff --git a/loadTest/docker/grafana/plugins/datadog/src/img/datadog.png b/loadTest/docker/grafana/plugins/datadog/src/img/datadog.png
new file mode 100644
index 0000000..0a5865f
Binary files /dev/null and b/loadTest/docker/grafana/plugins/datadog/src/img/datadog.png differ
diff --git a/loadTest/docker/grafana/plugins/datadog/src/module.js b/loadTest/docker/grafana/plugins/datadog/src/module.js
new file mode 100644
index 0000000..2bbb3cd
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/src/module.js
@@ -0,0 +1,19 @@
+import {DataDogDatasource} from './datasource';
+import {DataDogQueryCtrl} from './query_ctrl';
+
+class DataDogConfigCtrl {}
+DataDogConfigCtrl.templateUrl = "partials/config.html";
+
+class DataDogQueryOptionsCtrl {}
+DataDogQueryOptionsCtrl.templateUrl = "partials/query.options.html";
+
+class DataDogAnnotationsQueryCtrl {}
+DataDogAnnotationsQueryCtrl.templateUrl = 'partials/annotations.editor.html';
+
+export {
+ DataDogDatasource as Datasource,
+ DataDogQueryCtrl as QueryCtrl,
+ DataDogConfigCtrl as ConfigCtrl,
+ DataDogQueryOptionsCtrl as QueryOptionsCtrl,
+ DataDogAnnotationsQueryCtrl as AnnotationsQueryCtrl
+};
diff --git a/loadTest/docker/grafana/plugins/datadog/src/partials/annotations.editor.html b/loadTest/docker/grafana/plugins/datadog/src/partials/annotations.editor.html
new file mode 100644
index 0000000..5410b04
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/src/partials/annotations.editor.html
@@ -0,0 +1,29 @@
+
diff --git a/loadTest/docker/grafana/plugins/datadog/src/partials/config.html b/loadTest/docker/grafana/plugins/datadog/src/partials/config.html
new file mode 100644
index 0000000..edcc38e
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/src/partials/config.html
@@ -0,0 +1,35 @@
+
+
+
+
+
diff --git a/loadTest/docker/grafana/plugins/datadog/src/partials/query.editor.html b/loadTest/docker/grafana/plugins/datadog/src/partials/query.editor.html
new file mode 100644
index 0000000..ab9998f
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/src/partials/query.editor.html
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
diff --git a/loadTest/docker/grafana/plugins/datadog/src/partials/query.options.html b/loadTest/docker/grafana/plugins/datadog/src/partials/query.options.html
new file mode 100644
index 0000000..99042d6
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/src/partials/query.options.html
@@ -0,0 +1,2 @@
+
diff --git a/loadTest/docker/grafana/plugins/datadog/src/plugin.json b/loadTest/docker/grafana/plugins/datadog/src/plugin.json
new file mode 100644
index 0000000..912b71c
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/src/plugin.json
@@ -0,0 +1,34 @@
+{
+ "id": "grafana-datadog-datasource",
+ "name": "DataDog",
+ "type": "datasource",
+
+ "metrics": true,
+ "annotations": true,
+
+ "staticRoot": ".",
+
+ "info": {
+ "description": "datasource plugin for DataDog",
+ "author": {
+ "name": "Alex Kessinger",
+ "url": "http://rumproarious.com"
+ },
+ "keywords": ["datadog"],
+ "logos": {
+ "small": "img/datadog.png",
+ "large": "img/datadog.png"
+ },
+ "links": [
+ {"name": "GitHub", "url": "https://github.com/voidfiles/grafana-datasource-datagog"},
+ {"name": "License", "url": "https://github.com/voidfiles/grafana-datasource-datagog/blob/master/LICENSE"}
+ ],
+ "version": "0.0.1",
+ "updated": "2016-12-18"
+ },
+
+ "dependencies": {
+ "grafanaVersion": "3.x.x",
+ "plugins": [ ]
+ }
+}
diff --git a/loadTest/docker/grafana/plugins/datadog/src/query_builder.js b/loadTest/docker/grafana/plugins/datadog/src/query_builder.js
new file mode 100644
index 0000000..358033b
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/src/query_builder.js
@@ -0,0 +1,65 @@
+import _ from 'lodash';
+import dfunc from './dfunc';
+
+export function buildQuery(target, adhocFilters) {
+ let {aggregation, metric, tags, functions} = target;
+ let query = `${aggregation}:${metric}`;
+
+ let functionInstances = getFunctionInstances(functions);
+
+ if ((tags && tags.length) || (adhocFilters && adhocFilters.length)) {
+ query += '{';
+
+ if (tags && tags.length) {
+ query += tags.join(',');
+ }
+
+ if (adhocFilters && adhocFilters.length) {
+ let adhocTags = buildAdHocFilterString(adhocFilters);
+ if (tags && tags.length) {
+ query += ',';
+ }
+ query += adhocTags;
+ }
+
+ query += '}';
+ } else {
+ query += '{*}';
+ }
+
+ if (target.as) {
+ query += '.' + target.as + '()';
+ }
+
+ var groupedFuncs = _.groupBy(functionInstances, func => {
+ if (func.def.append) {
+ return 'appends';
+ } else {
+ return 'wraps';
+ }
+ });
+
+ _.each(groupedFuncs.appends, func => {
+ query += '.' + func.render();
+ });
+
+ _.each(groupedFuncs.wraps, func => {
+ query = func.render(query);
+ });
+
+ return query;
+}
+
+function getFunctionInstances(functions) {
+ return _.map(functions, func => {
+ var f = dfunc.createFuncInstance(func.funcDef, {withDefaultParams: false});
+ f.params = func.params.slice();
+ return f;
+ });
+}
+
+function buildAdHocFilterString(adhocFilters) {
+ return adhocFilters.map(filter => {
+ return filter.key + ':' + filter.value;
+ }).join(',');
+}
diff --git a/loadTest/docker/grafana/plugins/datadog/src/query_ctrl.js b/loadTest/docker/grafana/plugins/datadog/src/query_ctrl.js
new file mode 100644
index 0000000..c47e310
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/src/query_ctrl.js
@@ -0,0 +1,185 @@
+import _ from 'lodash';
+import dfunc from './dfunc';
+import {QueryCtrl} from 'app/plugins/sdk';
+import './func_editor';
+import './add_datadog_func';
+import * as queryBuilder from './query_builder';
+
+export class DataDogQueryCtrl extends QueryCtrl {
+
+ constructor($scope, $injector, uiSegmentSrv, templateSrv) {
+ super($scope, $injector);
+ this.removeText = '-- remove tag --';
+ this.uiSegmentSrv = uiSegmentSrv;
+ this.templateSrv = templateSrv;
+
+ if (this.target.aggregation) {
+ this.aggregationSegment = new uiSegmentSrv.newSegment(
+ this.target.aggregation
+ );
+ } else {
+ this.aggregationSegment = new uiSegmentSrv.newSegment({
+ value: 'Select Aggregation',
+ fake: true,
+ custom: false,
+ });
+ }
+
+ if (this.target.metric) {
+ this.metricSegment = new uiSegmentSrv.newSegment(
+ this.target.metric
+ );
+ } else {
+ this.metricSegment = new uiSegmentSrv.newSegment({
+ value: 'Select Metric',
+ fake: true,
+ custom: false,
+ });
+ }
+
+ this.target.tags = this.target.tags || [];
+ this.tagSegments = this.target.tags.map(uiSegmentSrv.newSegment);
+ this.fixTagSegments();
+
+ this.functions = [];
+ this.target.functions = this.target.functions || [];
+ this.functions = _.map(this.target.functions, func => {
+ var f = dfunc.createFuncInstance(func.funcDef, {withDefaultParams: false});
+ f.params = func.params.slice();
+ return f;
+ });
+
+ if (this.target.as) {
+ this.asSegment = uiSegmentSrv.newSegment(this.target.as);
+ } else {
+ this.asSegment = uiSegmentSrv.newSegment({
+ value: 'Select As',
+ fake: true,
+ custom: false,
+ });
+ }
+
+ }
+
+ toggleEditorMode() {
+ this.target.rawQuery = !this.target.rawQuery;
+ }
+
+ getMetrics() {
+ return this.datasource.metricFindQuery()
+ .then(this.uiSegmentSrv.transformToSegments(true));
+ }
+
+ getAggregations() {
+ return Promise.resolve([
+ {text: 'avg by', value: 'avg'},
+ {text: 'max by', value: 'max'},
+ {text: 'min by', value: 'min'},
+ {text: 'sub by', value: 'sum'},
+ ]);
+ }
+
+ getAs() {
+ return Promise.resolve([
+ {text: 'None', value: 'None'},
+ {text: 'as_count', value: 'as_count'},
+ {text: 'as_rate', value: 'as_rate'},
+ ]);
+ }
+
+ getTags(segment) {
+ return this.datasource.tagFindQuery()
+ .then(this.uiSegmentSrv.transformToSegments(true))
+ .then(results => {
+ if (segment.type !== 'plus-button') {
+ let removeSegment = this.uiSegmentSrv.newFake(this.removeText);
+ results.unshift(removeSegment);
+ }
+
+ return results;
+ });
+ }
+
+ aggregationChanged() {
+ this.target.aggregation = this.aggregationSegment.value;
+ this.panelCtrl.refresh();
+ }
+
+ metricChanged() {
+ this.target.metric = this.metricSegment.value;
+ this.panelCtrl.refresh();
+ }
+
+ asChanged() {
+ if (this.asSegment.value === 'None') {
+ this.target.as = null;
+ } else {
+ this.target.as = this.asSegment.value;
+ }
+ this.panelCtrl.refresh();
+ }
+
+ fixTagSegments() {
+ var count = this.tagSegments.length;
+ var lastSegment = this.tagSegments[Math.max(count-1, 0)];
+
+ if (!lastSegment || lastSegment.type !== 'plus-button') {
+ this.tagSegments.push(this.uiSegmentSrv.newPlusButton());
+ }
+ }
+
+ targetChanged() {
+ if (this.error) {
+ return;
+ }
+
+ this.panelCtrl.refresh();
+ }
+
+ persistFunctions () {
+ this.target.functions = _.map(this.functions, func => {
+ return {
+ funcDef: func.def.name,
+ params: func.params.slice(),
+ };
+ });
+ }
+
+ removeFunction(func) {
+ this.functions = _.without(this.functions, func);
+ this.persistFunctions();
+ this.targetChanged();
+ }
+
+ addFunction(funcDef) {
+ var func = dfunc.createFuncInstance(funcDef, {withDefaultParams: true});
+ func.added = true;
+ this.functions.push(func);
+ this.persistFunctions();
+ this.targetChanged();
+ }
+
+ tagSegmentUpdated(segment, index) {
+ if (segment.value === this.removeText) {
+ this.tagSegments.splice(index, 1);
+ }
+
+ let realSegments = _.filter(this.tagSegments, segment => segment.value);
+ this.target.tags = realSegments.map(segment => segment.value);
+
+ this.tagSegments = _.map(this.target.tags, this.uiSegmentSrv.newSegment);
+ this.fixTagSegments();
+
+ this.panelCtrl.refresh();
+ }
+
+ getCollapsedText() {
+ if (this.target.rawQuery) {
+ return this.target.query;
+ } else {
+ return queryBuilder.buildQuery(this.target);
+ }
+ }
+}
+
+DataDogQueryCtrl.templateUrl = 'partials/query.editor.html';
diff --git a/loadTest/docker/grafana/plugins/datadog/vendor/showdown.min.js b/loadTest/docker/grafana/plugins/datadog/vendor/showdown.min.js
new file mode 100644
index 0000000..80be78c
--- /dev/null
+++ b/loadTest/docker/grafana/plugins/datadog/vendor/showdown.min.js
@@ -0,0 +1,5 @@
+/*! showdown 29-01-2017 */
+
+(function(){function a(a){"use strict";var b={omitExtraWLInCodeBlocks:{defaultValue:!1,describe:"Omit the default extra whiteline added to code blocks",type:"boolean"},noHeaderId:{defaultValue:!1,describe:"Turn on/off generated header id",type:"boolean"},prefixHeaderId:{defaultValue:!1,describe:"Specify a prefix to generated header ids",type:"string"},ghCompatibleHeaderId:{defaultValue:!1,describe:"Generate header ids compatible with github style (spaces are replaced with dashes, a bunch of non alphanumeric chars are removed)",type:"boolean"},headerLevelStart:{defaultValue:!1,describe:"The header blocks level start",type:"integer"},parseImgDimensions:{defaultValue:!1,describe:"Turn on/off image dimension parsing",type:"boolean"},simplifiedAutoLink:{defaultValue:!1,describe:"Turn on/off GFM autolink style",type:"boolean"},excludeTrailingPunctuationFromURLs:{defaultValue:!1,describe:"Excludes trailing punctuation from links generated with autoLinking",type:"boolean"},literalMidWordUnderscores:{defaultValue:!1,describe:"Parse midword underscores as literal underscores",type:"boolean"},strikethrough:{defaultValue:!1,describe:"Turn on/off strikethrough support",type:"boolean"},tables:{defaultValue:!1,describe:"Turn on/off tables support",type:"boolean"},tablesHeaderId:{defaultValue:!1,describe:"Add an id to table headers",type:"boolean"},ghCodeBlocks:{defaultValue:!0,describe:"Turn on/off GFM fenced code blocks support",type:"boolean"},tasklists:{defaultValue:!1,describe:"Turn on/off GFM tasklist support",type:"boolean"},smoothLivePreview:{defaultValue:!1,describe:"Prevents weird effects in live previews due to incomplete input",type:"boolean"},smartIndentationFix:{defaultValue:!1,description:"Tries to smartly fix indentation in es6 strings",type:"boolean"},disableForced4SpacesIndentedSublists:{defaultValue:!1,description:"Disables the requirement of indenting nested sublists by 4 spaces",type:"boolean"},simpleLineBreaks:{defaultValue:!1,description:"Parses simple line breaks as
(GFM Style)",type:"boolean"},requireSpaceBeforeHeadingText:{defaultValue:!1,description:"Makes adding a space between `#` and the header text mandatory (GFM Style)",type:"boolean"},ghMentions:{defaultValue:!1,description:"Enables github @mentions",type:"boolean"},ghMentionsLink:{defaultValue:"https://github.com/{u}",description:"Changes the link generated by @mentions. Only applies if ghMentions option is enabled.",type:"string"},encodeEmails:{defaultValue:!0,description:"Encode e-mail addresses through the use of Character Entities, transforming ASCII e-mail addresses into its equivalent decimal entities",type:"boolean"}};if(a===!1)return JSON.parse(JSON.stringify(b));var c={};for(var d in b)b.hasOwnProperty(d)&&(c[d]=b[d].defaultValue);return c}function b(){"use strict";var b=a(!0),c={};for(var d in b)b.hasOwnProperty(d)&&(c[d]=!0);return c}function c(a,b){"use strict";var c=b?"Error in "+b+" extension->":"Error in unnamed extension",d={valid:!0,error:""};e.helper.isArray(a)||(a=[a]);for(var f=0;f-1,l=new RegExp(b+"|"+c,"g"+j.replace(/g/g,"")),m=new RegExp(b,j.replace(/g/g,"")),n=[];do for(e=0;g=l.exec(a);)if(m.test(g[0]))e++||(f=l.lastIndex,h=f-g[0].length);else if(e&&!--e){i=g.index+g[0].length;var o={left:{start:h,end:f},match:{start:f,end:g.index},right:{start:g.index,end:i},wholeMatch:{start:h,end:i}};if(n.push(o),!k)return n}while(e&&(l.lastIndex=f));return n};e.helper.matchRecursiveRegExp=function(a,b,c,d){"use strict";for(var e=k(a,b,c,d),f=[],g=0;g0){var l=[];0!==h[0].wholeMatch.start&&l.push(a.slice(0,h[0].wholeMatch.start));for(var m=0;j>m;++m)l.push(b(a.slice(h[m].wholeMatch.start,h[m].wholeMatch.end),a.slice(h[m].match.start,h[m].match.end),a.slice(h[m].left.start,h[m].left.end),a.slice(h[m].right.start,h[m].right.end))),j-1>m&&l.push(a.slice(h[m].wholeMatch.end,h[m+1].wholeMatch.start));h[j-1].wholeMatch.end.9?b[2](a):c>.45?b[1](a):b[0](a)}return a})},"undefined"==typeof console&&(console={warn:function(a){"use strict";alert(a)},log:function(a){"use strict";alert(a)},error:function(a){"use strict";throw a}}),e.helper.regexes={asteriskAndDash:/([*_])/g},e.Converter=function(a){"use strict";function b(){a=a||{};for(var b in h)h.hasOwnProperty(b)&&(m[b]=h[b]);if("object"!=typeof a)throw Error("Converter expects the passed parameter to be an object, but "+typeof a+" was passed instead.");for(var c in a)a.hasOwnProperty(c)&&(m[c]=a[c]);m.extensions&&e.helper.forEach(m.extensions,d)}function d(a,b){if(b=b||null,e.helper.isString(a)){if(a=e.helper.stdExtName(a),b=a,e.extensions[a])return console.warn("DEPRECATION WARNING: "+a+" is an old extension that uses a deprecated loading method.Please inform the developer that the extension should be updated!"),void f(e.extensions[a],a);if(e.helper.isUndefined(g[a]))throw Error('Extension "'+a+'" could not be loaded. It was either not found or is not a valid extension.');a=g[a]}"function"==typeof a&&(a=a()),e.helper.isArray(a)||(a=[a]);var d=c(a,b);if(!d.valid)throw Error(d.error);for(var h=0;h-1))return a;m=""}else m=c.gUrls[l],e.helper.isUndefined(c.gTitles[l])||(n=c.gTitles[l]);m=m.replace(e.helper.regexes.asteriskAndDash,e.helper.escapeCharactersCallback);var o='"+k+""};return a=a.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)][ ]?(?:\n[ ]*)?\[(.*?)])()()()()/g,d),a=a.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)]\([ \t]*()(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,d),a=a.replace(/(\[([^\[\]]+)])()()()()()/g,d),b.ghMentions&&(a=a.replace(/(^|\s)(\\)?(@([a-z\d\-]+))(?=[.!?;,[\]()]|\s|$)/gim,function(a,c,d,f,g){if("\\"===d)return c+f;if(!e.helper.isString(b.ghMentionsLink))throw new Error("ghMentionsLink option must be a string");var h=b.ghMentionsLink.replace(/\{u}/g,g);return c+''+f+""})),a=c.converter._dispatch("anchors.after",a,b,c)}),e.subParser("autoLinks",function(a,b,c){"use strict";function d(a,c,d,e,f){var g=c,h="";return/^www\./i.test(c)&&(c=c.replace(/^www\./i,"http://www.")),b.excludeTrailingPunctuationFromURLs&&f&&(h=f),''+g+""+h}function f(a,c,d){var f="mailto:";return c=c||"",d=e.subParser("unescapeSpecialChars")(d),b.encodeEmails?(d=e.helper.encodeEmailAddress(d),f=e.helper.encodeEmailAddress(f+d)):f+=d,c+''+d+""}a=c.converter._dispatch("autoLinks.before",a,b,c);var g=/\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)()(?=\s|$)(?!["<>])/gi,h=/\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+?)([.!?()]?)(?=\s|$)(?!["<>])/gi,i=/<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi,j=/(^|\s)(?:mailto:)?([A-Za-z0-9!#$%&'*+-\/=?^_`{|}~.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?=$|\s)/gim,k=/<()(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi;return a=a.replace(i,d),a=a.replace(k,f),b.simplifiedAutoLink&&(a=b.excludeTrailingPunctuationFromURLs?a.replace(h,d):a.replace(g,d),a=a.replace(j,f)),a=c.converter._dispatch("autoLinks.after",a,b,c)}),e.subParser("blockGamut",function(a,b,c){"use strict";return a=c.converter._dispatch("blockGamut.before",a,b,c),a=e.subParser("blockQuotes")(a,b,c),a=e.subParser("headers")(a,b,c),a=e.subParser("horizontalRule")(a,b,c),a=e.subParser("lists")(a,b,c),a=e.subParser("codeBlocks")(a,b,c),a=e.subParser("tables")(a,b,c),a=e.subParser("hashHTMLBlocks")(a,b,c),a=e.subParser("paragraphs")(a,b,c),a=c.converter._dispatch("blockGamut.after",a,b,c)}),e.subParser("blockQuotes",function(a,b,c){"use strict";return a=c.converter._dispatch("blockQuotes.before",a,b,c),a=a.replace(/((^ {0,3}>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(a,d){var f=d;return f=f.replace(/^[ \t]*>[ \t]?/gm,"¨0"),f=f.replace(/¨0/g,""),f=f.replace(/^[ \t]+$/gm,""),f=e.subParser("githubCodeBlocks")(f,b,c),f=e.subParser("blockGamut")(f,b,c),f=f.replace(/(^|\n)/g,"$1 "),f=f.replace(/(\s*[^\r]+?<\/pre>)/gm,function(a,b){var c=b;return c=c.replace(/^ /gm,"¨0"),c=c.replace(/¨0/g,"")}),e.subParser("hashBlock")("\n"+f+"\n
",b,c)}),a=c.converter._dispatch("blockQuotes.after",a,b,c)}),e.subParser("codeBlocks",function(a,b,c){"use strict";a=c.converter._dispatch("codeBlocks.before",a,b,c),a+="¨0";var d=/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=¨0))/g;return a=a.replace(d,function(a,d,f){var g=d,h=f,i="\n";return g=e.subParser("outdent")(g),g=e.subParser("encodeCode")(g),g=e.subParser("detab")(g),g=g.replace(/^\n+/g,""),g=g.replace(/\n+$/g,""),b.omitExtraWLInCodeBlocks&&(i=""),g=""+g+i+"
",e.subParser("hashBlock")(g,b,c)+h}),a=a.replace(/¨0/,""),a=c.converter._dispatch("codeBlocks.after",a,b,c)}),e.subParser("codeSpans",function(a,b,c){"use strict";return a=c.converter._dispatch("codeSpans.before",a,b,c),"undefined"==typeof a&&(a=""),a=a.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(a,b,c,d){var f=d;return f=f.replace(/^([ \t]*)/g,""),f=f.replace(/[ \t]*$/g,""),f=e.subParser("encodeCode")(f),b+""+f+""}),a=c.converter._dispatch("codeSpans.after",a,b,c)}),e.subParser("detab",function(a){"use strict";return a=a.replace(/\t(?=\t)/g," "),a=a.replace(/\t/g,"¨A¨B"),a=a.replace(/¨B(.+?)¨A/g,function(a,b){for(var c=b,d=4-c.length%4,e=0;d>e;e++)c+=" ";return c}),a=a.replace(/¨A/g," "),a=a.replace(/¨B/g,"")}),e.subParser("encodeAmpsAndAngles",function(a){"use strict";return a=a.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"),a=a.replace(/<(?![a-z\/?\$!])/gi,"<")}),e.subParser("encodeBackslashEscapes",function(a){"use strict";return a=a.replace(/\\(\\)/g,e.helper.escapeCharactersCallback),a=a.replace(/\\([`*_{}\[\]()>#+-.!~])/g,e.helper.escapeCharactersCallback)}),e.subParser("encodeCode",function(a){"use strict";return a=a.replace(/&/g,"&").replace(//g,">").replace(/([*_{}\[\]\\])/g,e.helper.escapeCharactersCallback)}),e.subParser("escapeSpecialCharsWithinTagAttributes",function(a){"use strict";var b=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi;return a=a.replace(b,function(a){return a.replace(/(.)<\/?code>(?=.)/g,"$1`").replace(/([\\`*_ ~=])/g,e.helper.escapeCharactersCallback)})}),e.subParser("githubCodeBlocks",function(a,b,c){"use strict";return b.ghCodeBlocks?(a=c.converter._dispatch("githubCodeBlocks.before",a,b,c),a+="¨0",a=a.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g,function(a,d,f){var g=b.omitExtraWLInCodeBlocks?"":"\n";return f=e.subParser("encodeCode")(f),f=e.subParser("detab")(f),f=f.replace(/^\n+/g,""),f=f.replace(/\n+$/g,""),f=""+f+g+"
",f=e.subParser("hashBlock")(f,b,c),"\n\n¨G"+(c.ghCodeBlocks.push({text:a,codeblock:f})-1)+"G\n\n"}),a=a.replace(/¨0/,""),c.converter._dispatch("githubCodeBlocks.after",a,b,c)):a}),e.subParser("hashBlock",function(a,b,c){"use strict";return a=a.replace(/(^\n+|\n+$)/g,""),"\n\n¨K"+(c.gHtmlBlocks.push(a)-1)+"K\n\n"}),e.subParser("hashElement",function(a,b,c){"use strict";return function(a,b){var d=b;return d=d.replace(/\n\n/g,"\n"),d=d.replace(/^\n/,""),d=d.replace(/\n+$/g,""),d="\n\n¨K"+(c.gHtmlBlocks.push(d)-1)+"K\n\n"}}),e.subParser("hashHTMLBlocks",function(a,b,c){"use strict";for(var d=["pre","div","h1","h2","h3","h4","h5","h6","blockquote","table","dl","ol","ul","script","noscript","form","fieldset","iframe","math","style","section","header","footer","nav","article","aside","address","audio","canvas","figure","hgroup","output","video","p"],f=function(a,b,d,e){var f=a;return-1!==d.search(/\bmarkdown\b/)&&(f=d+c.converter.makeHtml(b)+e),"\n\n¨K"+(c.gHtmlBlocks.push(f)-1)+"K\n\n"},g=0;g]*>",""+d[g]+">","gim");return a=a.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,e.subParser("hashElement")(a,b,c)),a=e.helper.replaceRecursiveRegExp(a,function(a){return"\n\n¨K"+(c.gHtmlBlocks.push(a)-1)+"K\n\n"},"^ {0,3}","gm"),a=a.replace(/(?:\n\n)( {0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,e.subParser("hashElement")(a,b,c))}),e.subParser("hashHTMLSpans",function(a,b,c){"use strict";for(var d=e.helper.matchRecursiveRegExp(a,"]*>","","gi"),f=0;f]*>\\s*]*>","^ {0,3}\\s*","gim")}),e.subParser("headers",function(a,b,c){"use strict";function d(a){var b,d;return d=h?a.replace(/ /g,"-").replace(/&/g,"").replace(/¨T/g,"").replace(/¨D/g,"").replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g,"").toLowerCase():a.replace(/[^\w]/g,"").toLowerCase(),c.hashLinkCounts[d]?b=d+"-"+c.hashLinkCounts[d]++:(b=d,c.hashLinkCounts[d]=1),f===!0&&(f="section"),e.helper.isString(f)?f+b:b}a=c.converter._dispatch("headers.before",a,b,c);var f=b.prefixHeaderId,g=isNaN(parseInt(b.headerLevelStart))?1:parseInt(b.headerLevelStart),h=b.ghCompatibleHeaderId,i=b.smoothLivePreview?/^(.+)[ \t]*\n={2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n=+[ \t]*\n+/gm,j=b.smoothLivePreview?/^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n-+[ \t]*\n+/gm;a=a.replace(i,function(a,f){var h=e.subParser("spanGamut")(f,b,c),i=b.noHeaderId?"":' id="'+d(f)+'"',j=g,k=""+h+"";return e.subParser("hashBlock")(k,b,c)}),a=a.replace(j,function(a,f){var h=e.subParser("spanGamut")(f,b,c),i=b.noHeaderId?"":' id="'+d(f)+'"',j=g+1,k=""+h+"";return e.subParser("hashBlock")(k,b,c)});var k=b.requireSpaceBeforeHeadingText?/^(#{1,6})[ \t]+(.+?)[ \t]*#*\n+/gm:/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm;return a=a.replace(k,function(a,f,h){var i=e.subParser("spanGamut")(h,b,c),j=b.noHeaderId?"":' id="'+d(h)+'"',k=g-1+f.length,l=""+i+"";return e.subParser("hashBlock")(l,b,c)}),a=c.converter._dispatch("headers.after",a,b,c)}),e.subParser("horizontalRule",function(a,b,c){"use strict";a=c.converter._dispatch("horizontalRule.before",a,b,c);var d=e.subParser("hashBlock")("
",b,c);return a=a.replace(/^ {0,2}( ?-){3,}[ \t]*$/gm,d),a=a.replace(/^ {0,2}( ?\*){3,}[ \t]*$/gm,d),a=a.replace(/^ {0,2}( ?_){3,}[ \t]*$/gm,d),a=c.converter._dispatch("horizontalRule.after",a,b,c)}),e.subParser("images",function(a,b,c){"use strict";function d(a,b,d,f,g,h,i,j){var k=c.gUrls,l=c.gTitles,m=c.gDimensions;if(d=d.toLowerCase(),j||(j=""),""===f||null===f){if((""===d||null===d)&&(d=b.toLowerCase().replace(/ ?\n/g," ")),f="#"+d,e.helper.isUndefined(k[d]))return a;f=k[d],e.helper.isUndefined(l[d])||(j=l[d]),e.helper.isUndefined(m[d])||(g=m[d].width,h=m[d].height)}b=b.replace(/"/g,""").replace(e.helper.regexes.asteriskAndDash,e.helper.escapeCharactersCallback),f=f.replace(e.helper.regexes.asteriskAndDash,e.helper.escapeCharactersCallback);var n='
"}a=c.converter._dispatch("images.before",a,b,c);var f=/!\[(.*?)]\s?\([ \t]*()(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g,g=/!\[([^\]]*?)] ?(?:\n *)?\[(.*?)]()()()()()/g;return a=a.replace(g,d),a=a.replace(f,d),a=c.converter._dispatch("images.after",a,b,c)}),e.subParser("italicsAndBold",function(a,b,c){"use strict";return a=c.converter._dispatch("italicsAndBold.before",a,b,c),b.literalMidWordUnderscores?(a=a.replace(/(^|\s|>|\b)__(?=\S)([\s\S]+?)__(?=\b|<|\s|$)/gm,"$1$2"),a=a.replace(/(^|\s|>|\b)_(?=\S)([\s\S]+?)_(?=\b|<|\s|$)/gm,"$1$2"),a=a.replace(/(\*\*)(?=\S)([^\r]*?\S[*]*)\1/g,"$2"),a=a.replace(/(\*)(?=\S)([^\r]*?\S)\1/g,"$2")):(a=a.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,"$2"),a=a.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"$2")),a=c.converter._dispatch("italicsAndBold.after",a,b,c)}),e.subParser("lists",function(a,b,c){"use strict";function d(a,d){c.gListLevel++,a=a.replace(/\n{2,}$/,"\n"),a+="¨0";var f=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0| {0,3}([*+-]|\d+[.])[ \t]+))/gm,g=/\n[ \t]*\n(?!¨0)/.test(a);return b.disableForced4SpacesIndentedSublists&&(f=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0|\2([*+-]|\d+[.])[ \t]+))/gm),a=a.replace(f,function(a,d,f,h,i,j,k){k=k&&""!==k.trim();var l=e.subParser("outdent")(i,b,c),m="";return j&&b.tasklists&&(m=' class="task-list-item" style="list-style-type: none;"',l=l.replace(/^[ \t]*\[(x|X| )?]/m,function(){var a='"})),l=l.replace(/^([-*+]|\d\.)[ \t]+[\S\n ]*/g,function(a){return"¨A"+a}),d||l.search(/\n{2,}/)>-1?(l=e.subParser("githubCodeBlocks")(l,b,c),l=e.subParser("blockGamut")(l,b,c)):(l=e.subParser("lists")(l,b,c),l=l.replace(/\n$/,""),l=e.subParser("hashHTMLBlocks")(l,b,c),l=l.replace(/\n\n+/g,"\n\n"),l=l.replace(/\n\n/g,"¨B"),l=g?e.subParser("paragraphs")(l,b,c):e.subParser("spanGamut")(l,b,c),l=l.replace(/¨B/g,"\n\n")),l=l.replace("¨A",""),l=""+l+"\n"}),a=a.replace(/¨0/g,""),c.gListLevel--,d&&(a=a.replace(/\s+$/,"")),a}function f(a,c,e){var f=b.disableForced4SpacesIndentedSublists?/^ ?\d+\.[ \t]/gm:/^ {0,3}\d+\.[ \t]/gm,g=b.disableForced4SpacesIndentedSublists?/^ ?[*+-][ \t]/gm:/^ {0,3}[*+-][ \t]/gm,h="ul"===c?f:g,i="";return-1!==a.search(h)?!function j(a){var b=a.search(h);-1!==b?(i+="\n<"+c+">\n"+d(a.slice(0,b),!!e)+""+c+">\n",c="ul"===c?"ol":"ul",h="ul"===c?f:g,j(a.slice(b))):i+="\n<"+c+">\n"+d(a,!!e)+""+c+">\n"}(a):i="\n<"+c+">\n"+d(a,!!e)+""+c+">\n",i}return a=c.converter._dispatch("lists.before",a,b,c),a+="¨0",a=c.gListLevel?a.replace(/^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(a,b,c){var d=c.search(/[*+-]/g)>-1?"ul":"ol";return f(b,d,!0)}):a.replace(/(\n\n|^\n?)(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(a,b,c,d){var e=d.search(/[*+-]/g)>-1?"ul":"ol";return f(c,e,!1)}),a=a.replace(/¨0/,""),a=c.converter._dispatch("lists.after",a,b,c)}),e.subParser("outdent",function(a){"use strict";return a=a.replace(/^(\t|[ ]{1,4})/gm,"¨0"),a=a.replace(/¨0/g,"")}),e.subParser("paragraphs",function(a,b,c){"use strict";a=c.converter._dispatch("paragraphs.before",a,b,c),a=a.replace(/^\n+/g,""),a=a.replace(/\n+$/g,"");for(var d=a.split(/\n{2,}/g),f=[],g=d.length,h=0;g>h;h++){var i=d[h];i.search(/¨(K|G)(\d+)\1/g)>=0?f.push(i):(i=e.subParser("spanGamut")(i,b,c),i=i.replace(/^([ \t]*)/g,""),i+="
",f.push(i))}for(g=f.length,h=0;g>h;h++){for(var j="",k=f[h],l=!1;k.search(/¨(K|G)(\d+)\1/)>=0;){var m=RegExp.$1,n=RegExp.$2;j="K"===m?c.gHtmlBlocks[n]:l?e.subParser("encodeCode")(c.ghCodeBlocks[n].text):c.ghCodeBlocks[n].codeblock,j=j.replace(/\$/g,"$$$$"),k=k.replace(/(\n\n)?¨(K|G)\d+\2(\n\n)?/,j),/^]*>\s*]*>/.test(k)&&(l=!0)}f[h]=k}return a=f.join("\n"),a=a.replace(/^\n+/g,""),a=a.replace(/\n+$/g,""),c.converter._dispatch("paragraphs.after",a,b,c)}),e.subParser("runExtension",function(a,b,c,d){"use strict";if(a.filter)b=a.filter(b,d.converter,c);else if(a.regex){var e=a.regex;!e instanceof RegExp&&(e=new RegExp(e,"g")),b=b.replace(e,a.replace)}return b}),e.subParser("spanGamut",function(a,b,c){"use strict";return a=c.converter._dispatch("spanGamut.before",a,b,c),a=e.subParser("codeSpans")(a,b,c),a=e.subParser("escapeSpecialCharsWithinTagAttributes")(a,b,c),a=e.subParser("encodeBackslashEscapes")(a,b,c),a=e.subParser("images")(a,b,c),a=e.subParser("anchors")(a,b,c),a=e.subParser("autoLinks")(a,b,c),a=e.subParser("encodeAmpsAndAngles")(a,b,c),a=e.subParser("italicsAndBold")(a,b,c),a=e.subParser("strikethrough")(a,b,c),a=b.simpleLineBreaks?a.replace(/\n/g,"
\n"):a.replace(/ +\n/g,"
\n"),a=c.converter._dispatch("spanGamut.after",a,b,c)}),e.subParser("strikethrough",function(a,b,c){"use strict";return b.strikethrough&&(a=c.converter._dispatch("strikethrough.before",a,b,c),a=a.replace(/(?:~){2}([\s\S]+?)(?:~){2}/g,"$1"),a=c.converter._dispatch("strikethrough.after",a,b,c)),a}),e.subParser("stripBlankLines",function(a){"use strict";return a.replace(/^[ \t]+$/gm,"")}),e.subParser("stripLinkDefinitions",function(a,b,c){"use strict";var d=/^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=¨0))/gm;return a+="¨0",a=a.replace(d,function(a,d,f,g,h,i,j){return d=d.toLowerCase(),c.gUrls[d]=e.subParser("encodeAmpsAndAngles")(f),i?i+j:(j&&(c.gTitles[d]=j.replace(/"|'/g,""")),b.parseImgDimensions&&g&&h&&(c.gDimensions[d]={width:g,height:h}),"")}),a=a.replace(/¨0/,"")}),e.subParser("tables",function(a,b,c){"use strict";function d(a){return/^:[ \t]*--*$/.test(a)?' style="text-align:left;"':/^--*[ \t]*:[ \t]*$/.test(a)?' style="text-align:right;"':/^:[ \t]*--*[ \t]*:$/.test(a)?' style="text-align:center;"':""}function f(a,d){var f="";return a=a.trim(),b.tableHeaderId&&(f=' id="'+a.replace(/ /g,"_").toLowerCase()+'"'),a=e.subParser("spanGamut")(a,b,c),""+a+" | \n"}function g(a,d){var f=e.subParser("spanGamut")(a,b,c);return""+f+" | \n"}function h(a,b){for(var c="\n\n\n",d=a.length,e=0;d>e;++e)c+=a[e];for(c+="
\n\n\n",e=0;e\n";for(var f=0;d>f;++f)c+=b[e][f];c+="\n"}return c+="\n
\n"}if(!b.tables)return a;var i=/^ {0,3}\|?.+\|.+\n[ \t]{0,3}\|?[ \t]*:?[ \t]*(?:-|=){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:-|=){2,}[\s\S]+?(?:\n\n|¨0)/gm;return a=c.converter._dispatch("tables.before",a,b,c),a=a.replace(i,function(a){var b,c=a.split("\n");for(b=0;b 0) {
+ let ran = Math.floor(Math.random() * clone.length);
+ let currentItems = clone.splice(ran, Math.floor(Math.random() * 3 + 1));
+
+ k6.sleep(Math.round(Math.random() * 20 + 1));
+
+ for (let i = 0; i < currentItems.length; i++) {
+ k6.group(currentItems[i].fileName.substring(7), function () {
+ utils.runHttp(currentItems[i], token);
+ });
+ }
+ }
+}
+
diff --git a/loadTest/docker/testRunner/scripts/utils.js b/loadTest/docker/testRunner/scripts/utils.js
new file mode 100644
index 0000000..14361e5
--- /dev/null
+++ b/loadTest/docker/testRunner/scripts/utils.js
@@ -0,0 +1,67 @@
+const http = require("k6/http");
+const isLogging = false;
+
+export function runHttp(jsonBlob, token) {
+ let response;
+ if (isLogging) console.log("running file: ", jsonBlob.fileName)
+ let json = JSON.stringify(jsonBlob.text);
+
+ const startDay = Math.floor(Math.random() * 30) + 1;
+ const endDay = Math.floor(Math.random() * 30) + 1;
+ json = json.replace("{startDay}", startDay);
+ json = json.replace("{endDay}", endDay);
+ json = JSON.parse(json);
+ response = http.post(
+ 'https://analytics-api-perf.dev.stratanetwork.net/api/v1/data-sources/1307/query',
+ json,
+ {
+ headers: {
+ authorization: `Bearer ${token}`,
+ Accept: '*/*',
+ "Accept-Encoding": "gzip, deflate, br",
+ "Clientappname": 'strata-analytics',
+ 'content-type': 'application/json',
+ 'sec-ch-ua': '"Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
+ 'sec-ch-ua-mobile': '?0',
+ 'sec-ch-ua-platform': '"Windows"',
+ },
+ }
+ )
+ if(response.status !== 200) console.log("endpoint failed", jsonBlob.fileName);
+ else if (isLogging) console.log("success: ", jsonBlob.fileName);
+}
+
+export function generateToken() {
+ const url = "https://identity.dev.stratanetwork.net";
+ const username = `perf1`;
+ const password = "Password1";
+ const dbguid = 'b8018250-af34-403e-bdcc-1770714042b2';
+
+ // token
+ const tokenUrl = url + "/connect/token";
+ const body = {
+ database_guid: dbguid,
+ grant_type: 'password',
+ username: username,
+ password: password,
+ scope: 'strata.api.external offline_access',
+ client_id: 'IntegrationTest',
+ client_secret: 'UhAEQsGByTCrwPdHPLmaet_DmLEfcJVbZqGBxArdeRup_TPLrYKNpUNRHejjdnhd',
+ };
+ const params = {
+ headers: {
+ 'content-type': 'application/x-www-form-urlencoded',
+ 'sec-ch-ua': '"Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
+ 'sec-ch-ua-mobile': '?0',
+ 'sec-ch-ua-platform': '"Windows"',
+ }
+ };
+ const response = http.post(tokenUrl, body, params);
+ if (response.status !== 200) {
+ if (isLogging) console.log("error getting token: ", response);
+ return false
+ };
+ const token = JSON.parse(response.body).access_token;
+ if (isLogging) console.log("success getting token! access token: ", token);
+ return token;
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/BestPracticeWorkbook.mjs b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/BestPracticeWorkbook.mjs
new file mode 100644
index 0000000..4091e9a
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/BestPracticeWorkbook.mjs
@@ -0,0 +1,66 @@
+import { sleep, group } from "k6";
+import http from "k6/http";
+
+
+// Best Practice Workbook
+module.exports = function main(token) {
+ let response;
+ console.log("inside the workbook");
+
+ const startDay = Math.floor(Math.random() * 31) + 1;
+ const endDay = Math.floor(Math.random() * 31) + 1;
+ response = http.post(
+ 'https://analytics-api-perf.dev.stratanetwork.net/api/v1/data-marts/1307/query',
+ `{"rows":[{"name":"Rollup","dataColumnId":575222,"columnAlias":"ROLLUP_AOO3Z","sortDirection":"asc"}],"values":[{"name":"2019_Cases","dataColumnId":585958,"calculationDataColumnIds":[585958],"aliasedIds":["585958"],"columnAlias":"PIVOT_ENCOUNTERRECORDNUMBER_265SK_0","groupValueIndex":0,"groupId":"FS9QQ","valueId":"PZTGR","calculation":"count(distinct [585958])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"K8JW6","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_VRHW7","columnType":"date","values":["2019"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["585958"]}]}]},{"name":"2020_Cases","dataColumnId":585958,"calculationDataColumnIds":[585958],"aliasedIds":["585958"],"columnAlias":"PIVOT_ENCOUNTERRECORDNUMBER_265SK_1","groupValueIndex":1,"groupId":"FS9QQ","valueId":"PZTGR","calculation":"count(distinct [585958])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"K8JW6","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_VRHW7","columnType":"date","values":["2020"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["585958"]}]}]},{"name":"2019_Total Charges","dataColumnId":586855,"calculationDataColumnIds":[586855],"aliasedIds":["586855"],"columnAlias":"PIVOT_TOTALCHARGES_48H1J_0","groupValueIndex":0,"groupId":"FS9QQ","valueId":"D8RZO","calculation":"sum([586855])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"K8JW6","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_VRHW7","columnType":"date","values":["2019"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586855"]}]}]},{"name":"2020_Total Charges","dataColumnId":586855,"calculationDataColumnIds":[586855],"aliasedIds":["586855"],"columnAlias":"PIVOT_TOTALCHARGES_48H1J_1","groupValueIndex":1,"groupId":"FS9QQ","valueId":"D8RZO","calculation":"sum([586855])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"K8JW6","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_VRHW7","columnType":"date","values":["2020"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586855"]}]}]},{"name":"2019_Total Costs","dataColumnId":586856,"calculationDataColumnIds":[586856],"aliasedIds":["586856"],"columnAlias":"PIVOT_TOTALCOSTS_LPFBQ_0","groupValueIndex":0,"groupId":"FS9QQ","valueId":"K5GVD","calculation":"sum([586856])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"K8JW6","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_VRHW7","columnType":"date","values":["2019"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586856"]}]}]},{"name":"2020_Total Costs","dataColumnId":586856,"calculationDataColumnIds":[586856],"aliasedIds":["586856"],"columnAlias":"PIVOT_TOTALCOSTS_LPFBQ_1","groupValueIndex":1,"groupId":"FS9QQ","valueId":"K5GVD","calculation":"sum([586856])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"K8JW6","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_VRHW7","columnType":"date","values":["2020"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586856"]}]}]}],"warnings":[],"filterGroups":[{"logicalOperator":"and","filters":[{"id":"N1Q3F","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_AHA9P","columnType":"date","values":["01/${startDay}/2018","01/${endDay}/2020"],"conditions":[],"filterType":"calendar","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[539377]}]}],"withTotals":true,"rowLimit":10000}`,
+ {
+ headers: {
+ authorization: `Bearer ${token}`,
+ Accept: '*/*',
+ "Accept-Encoding": "gzip, deflate, br",
+ "Clientappname": 'strata-analytics',
+ 'content-type': 'application/json',
+ 'sec-ch-ua': '"Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
+ 'sec-ch-ua-mobile': '?0',
+ 'sec-ch-ua-platform': '"Windows"',
+ },
+ }
+ )
+ console.log(response.status, "status code");
+
+ response = http.post(
+ 'https://analytics-api.dev.stratanetwork.net/api/v1/data-marts/1307/datacolumn/values',
+ '{"dataColumnId":539377,"datePart":"year","filter":{"id":"GFETD","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_ZIC1I","columnType":"date","values":["2019","2020"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[]},"rowLimit":31}',
+ {
+ headers: {
+ authorization: `Bearer ${token}`,
+ clientappname: 'strata-analytics',
+ 'content-type': 'application/json',
+ 'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
+ 'sec-ch-ua-mobile': '?0',
+ 'sec-ch-ua-platform': '"Windows"',
+ },
+ }
+ )
+
+ response = http.post(
+ 'https://analytics-api.dev.stratanetwork.net/api/v1/data-marts/1307/query',
+ // add randomization
+ `{"rows":[{"name":"Patient Type","dataColumnId":564543,"columnAlias":"NAME_OXV4H","sortDirection":"asc"}],"values":[{"name":"2019_Total Charges","dataColumnId":586855,"calculationDataColumnIds":[586855],"aliasedIds":["586855"],"columnAlias":"PIVOT_TOTALCHARGES_RK2QL_0","groupValueIndex":0,"groupId":"NNXYG","valueId":"ES7QJ","calculation":"sum([586855])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"GFETD","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_ZIC1I","columnType":"date","values":["2019"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586855"]}]}]},{"name":"2020_Total Charges","dataColumnId":586855,"calculationDataColumnIds":[586855],"aliasedIds":["586855"],"columnAlias":"PIVOT_TOTALCHARGES_RK2QL_1","groupValueIndex":1,"groupId":"NNXYG","valueId":"ES7QJ","calculation":"sum([586855])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"GFETD","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_ZIC1I","columnType":"date","values":["2020"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586855"]}]}]},{"name":"2019_Total Costs","dataColumnId":586856,"calculationDataColumnIds":[586856],"aliasedIds":["586856"],"columnAlias":"PIVOT_TOTALCOSTS_NIT3O_0","groupValueIndex":0,"groupId":"NNXYG","valueId":"F6OR3","calculation":"sum([586856])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"GFETD","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_ZIC1I","columnType":"date","values":["2019"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586856"]}]}]},{"name":"2020_Total Costs","dataColumnId":586856,"calculationDataColumnIds":[586856],"aliasedIds":["586856"],"columnAlias":"PIVOT_TOTALCOSTS_NIT3O_1","groupValueIndex":1,"groupId":"NNXYG","valueId":"F6OR3","calculation":"sum([586856])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"GFETD","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_ZIC1I","columnType":"date","values":["2020"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586856"]}]}]},{"name":"2019_Total Actual Payments","dataColumnId":586854,"calculationDataColumnIds":[586854],"aliasedIds":["586854"],"columnAlias":"PIVOT_TOTALACTUALPAYMENTS_O29XX_0","groupValueIndex":0,"groupId":"NNXYG","valueId":"YAE7N","calculation":"sum([586854])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"GFETD","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_ZIC1I","columnType":"date","values":["2019"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586854"]}]}]},{"name":"2020_Total Actual Payments","dataColumnId":586854,"calculationDataColumnIds":[586854],"aliasedIds":["586854"],"columnAlias":"PIVOT_TOTALACTUALPAYMENTS_O29XX_1","groupValueIndex":1,"groupId":"NNXYG","valueId":"YAE7N","calculation":"sum([586854])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"GFETD","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_ZIC1I","columnType":"date","values":["2020"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586854"]}]}]}],"warnings":[],"filterGroups":[{"logicalOperator":"and","filters":[{"id":"221UQ","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_MD2OV","columnType":"date","values":["2019","2020"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[539377]}]}],"withTotals":true,"rowLimit":10000}`,
+ {
+ headers: {
+ authorization: `Bearer ${token}`,
+ clientappname: 'strata-analytics',
+ 'content-type': 'application/json',
+ 'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
+ 'sec-ch-ua-mobile': '?0',
+ 'sec-ch-ua-platform': '"Windows"',
+ },
+ }
+ )
+}
+
+
+
+
+
+
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/CasesByServiceLineAndCM.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/CasesByServiceLineAndCM.json
new file mode 100644
index 0000000..2868825
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/CasesByServiceLineAndCM.json
@@ -0,0 +1,188 @@
+{
+ "rows": [
+ {
+ "name": "Rollup",
+ "dataColumnId": 575222,
+ "columnAlias": "ROLLUP_B16G7",
+ "sortDirection": "asc"
+ }
+ ],
+ "values": [
+ {
+ "name": "0-17_Encounter Record Number",
+ "dataColumnId": 585958,
+ "calculationDataColumnIds": [
+ 585958
+ ],
+ "aliasedIds": [
+ "585958"
+ ],
+ "columnAlias": "PIVOT_ENCOUNTERRECORDNUMBER_MFUF8_0",
+ "groupValueIndex": 0,
+ "groupId": "NQ6DZ",
+ "valueId": "U8KU2",
+ "calculation": "count(distinct [585958])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "JE94H",
+ "dataMartId": 1307,
+ "name": "Rollup",
+ "dataColumnName": "Rollup",
+ "dataColumnId": 527444,
+ "sqlColumnName": "ROLLUP_HXIUV",
+ "columnType": "text",
+ "values": [
+ "0-17"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "585958"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "18-64_Encounter Record Number",
+ "dataColumnId": 585958,
+ "calculationDataColumnIds": [
+ 585958
+ ],
+ "aliasedIds": [
+ "585958"
+ ],
+ "columnAlias": "PIVOT_ENCOUNTERRECORDNUMBER_MFUF8_1",
+ "groupValueIndex": 1,
+ "groupId": "NQ6DZ",
+ "valueId": "U8KU2",
+ "calculation": "count(distinct [585958])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "JE94H",
+ "dataMartId": 1307,
+ "name": "Rollup",
+ "dataColumnName": "Rollup",
+ "dataColumnId": 527444,
+ "sqlColumnName": "ROLLUP_HXIUV",
+ "columnType": "text",
+ "values": [
+ "18-64"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "585958"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "65 and Over_Encounter Record Number",
+ "dataColumnId": 585958,
+ "calculationDataColumnIds": [
+ 585958
+ ],
+ "aliasedIds": [
+ "585958"
+ ],
+ "columnAlias": "PIVOT_ENCOUNTERRECORDNUMBER_MFUF8_2",
+ "groupValueIndex": 2,
+ "groupId": "NQ6DZ",
+ "valueId": "U8KU2",
+ "calculation": "count(distinct [585958])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "JE94H",
+ "dataMartId": 1307,
+ "name": "Rollup",
+ "dataColumnName": "Rollup",
+ "dataColumnId": 527444,
+ "sqlColumnName": "ROLLUP_HXIUV",
+ "columnType": "text",
+ "values": [
+ "65 and Over"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "585958"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "warnings": [],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "DCCKX",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_3Z5DS",
+ "columnType": "date",
+ "values": [
+ "01/{startDay}/2018",
+ "01/{endDay}/2020"
+ ],
+ "conditions": [],
+ "filterType": "calendar",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 539377
+ ]
+ }
+ ]
+ }
+ ],
+ "withTotals": true,
+ "rowLimit": 10000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/GrossRevenueByServiceLine.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/GrossRevenueByServiceLine.json
new file mode 100644
index 0000000..ee40ebf
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/GrossRevenueByServiceLine.json
@@ -0,0 +1,303 @@
+{
+ "rows": [
+ {
+ "name": "Rollup",
+ "dataColumnId": 575222,
+ "columnAlias": "ROLLUP_85QAA",
+ "sortDirection": "asc"
+ }
+ ],
+ "values": [
+ {
+ "name": "01-2019_Net Revenue",
+ "dataColumnId": 586760,
+ "calculationDataColumnIds": [
+ 586760
+ ],
+ "aliasedIds": [
+ "586760"
+ ],
+ "columnAlias": "PIVOT_NETREVENUE_CIY57_0",
+ "groupValueIndex": 0,
+ "groupId": "TMOS9",
+ "valueId": "WJ6BD",
+ "calculation": "sum([586760])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "NE3DE",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_OL9IX",
+ "columnType": "date",
+ "values": [
+ "01-2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "month",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586760"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "02-2019_Net Revenue",
+ "dataColumnId": 586760,
+ "calculationDataColumnIds": [
+ 586760
+ ],
+ "aliasedIds": [
+ "586760"
+ ],
+ "columnAlias": "PIVOT_NETREVENUE_CIY57_1",
+ "groupValueIndex": 1,
+ "groupId": "TMOS9",
+ "valueId": "WJ6BD",
+ "calculation": "sum([586760])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "NE3DE",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_OL9IX",
+ "columnType": "date",
+ "values": [
+ "02-2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "month",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586760"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "03-2019_Net Revenue",
+ "dataColumnId": 586760,
+ "calculationDataColumnIds": [
+ 586760
+ ],
+ "aliasedIds": [
+ "586760"
+ ],
+ "columnAlias": "PIVOT_NETREVENUE_CIY57_2",
+ "groupValueIndex": 2,
+ "groupId": "TMOS9",
+ "valueId": "WJ6BD",
+ "calculation": "sum([586760])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "NE3DE",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_OL9IX",
+ "columnType": "date",
+ "values": [
+ "03-2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "month",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586760"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "04-2019_Net Revenue",
+ "dataColumnId": 586760,
+ "calculationDataColumnIds": [
+ 586760
+ ],
+ "aliasedIds": [
+ "586760"
+ ],
+ "columnAlias": "PIVOT_NETREVENUE_CIY57_3",
+ "groupValueIndex": 3,
+ "groupId": "TMOS9",
+ "valueId": "WJ6BD",
+ "calculation": "sum([586760])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "NE3DE",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_OL9IX",
+ "columnType": "date",
+ "values": [
+ "04-2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "month",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586760"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "05-2019_Net Revenue",
+ "dataColumnId": 586760,
+ "calculationDataColumnIds": [
+ 586760
+ ],
+ "aliasedIds": [
+ "586760"
+ ],
+ "columnAlias": "PIVOT_NETREVENUE_CIY57_4",
+ "groupValueIndex": 4,
+ "groupId": "TMOS9",
+ "valueId": "WJ6BD",
+ "calculation": "sum([586760])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "NE3DE",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_OL9IX",
+ "columnType": "date",
+ "values": [
+ "05-2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "month",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586760"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "06-2019_Net Revenue",
+ "dataColumnId": 586760,
+ "calculationDataColumnIds": [
+ 586760
+ ],
+ "aliasedIds": [
+ "586760"
+ ],
+ "columnAlias": "PIVOT_NETREVENUE_CIY57_5",
+ "groupValueIndex": 5,
+ "groupId": "TMOS9",
+ "valueId": "WJ6BD",
+ "calculation": "sum([586760])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "NE3DE",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_OL9IX",
+ "columnType": "date",
+ "values": [
+ "06-2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "month",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586760"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "warnings": [],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": []
+ }
+ ],
+ "withTotals": true,
+ "rowLimit": 10000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OrthopedicCosts.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OrthopedicCosts.json
new file mode 100644
index 0000000..9608c29
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OrthopedicCosts.json
@@ -0,0 +1,141 @@
+{
+ "rows": [
+ {
+ "name": "Rollup",
+ "dataColumnId": 575222,
+ "columnAlias": "ROLLUP_VX6NF",
+ "sortDirection": "asc"
+ },
+ {
+ "name": "Case Mix Group",
+ "dataColumnId": 530020,
+ "columnAlias": "NAME_D6Y37",
+ "sortDirection": "asc"
+ },
+ {
+ "name": "Encounter Record Number",
+ "dataColumnId": 562870,
+ "columnAlias": "ENCOUNTERRECORDNUMBER_8TTDW",
+ "sortDirection": "asc"
+ }
+ ],
+ "values": [
+ {
+ "name": "Total Charges",
+ "dataColumnId": 586855,
+ "calculationDataColumnIds": [
+ 586855
+ ],
+ "aliasedIds": [
+ "586855"
+ ],
+ "columnAlias": "VALUE_TOTALCHARGES_VAB3W_0",
+ "groupId": "FHCM9",
+ "valueId": "7HYFA",
+ "calculation": "sum([586855])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": []
+ }
+ ]
+ },
+ {
+ "name": "Total Costs",
+ "dataColumnId": 586856,
+ "calculationDataColumnIds": [
+ 586856
+ ],
+ "aliasedIds": [
+ "586856"
+ ],
+ "columnAlias": "VALUE_TOTALCOSTS_64PFM_1",
+ "groupId": "FHCM9",
+ "valueId": "UV541",
+ "calculation": "sum([586856])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": []
+ }
+ ]
+ },
+ {
+ "name": "Net Revenue",
+ "dataColumnId": 586760,
+ "calculationDataColumnIds": [
+ 586760
+ ],
+ "aliasedIds": [
+ "586760"
+ ],
+ "columnAlias": "VALUE_NETREVENUE_LTUVC_2",
+ "groupId": "FHCM9",
+ "valueId": "1QKYV",
+ "calculation": "sum([586760])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": []
+ }
+ ]
+ }
+ ],
+ "warnings": [],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "T47AV",
+ "dataMartId": 1307,
+ "name": "Rollup",
+ "dataColumnName": "Rollup",
+ "dataColumnId": 575222,
+ "sqlColumnName": "ROLLUP_ET1NC",
+ "columnType": "text",
+ "values": [
+ "Orthopedics"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": []
+ },
+ {
+ "id": "KPL54",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_Q3UU2",
+ "columnType": "date",
+ "values": [
+ "01/{startDay}/2019",
+ "01/{endDay}/2023"
+ ],
+ "conditions": [],
+ "filterType": "calendar",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 539377
+ ]
+ }
+ ]
+ }
+ ],
+ "withTotals": true,
+ "rowLimit": 10000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/CasesByServiceLine.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/CasesByServiceLine.json
new file mode 100644
index 0000000..6df5829
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/CasesByServiceLine.json
@@ -0,0 +1,62 @@
+{
+ "rows": [
+ {
+ "dataColumnId": 575222,
+ "columnAlias": "ROLLUP_OYJKZ",
+ "sortDirection": "asc"
+ }
+ ],
+ "values": [
+ {
+ "dataColumnId": 585958,
+ "calculationDataColumnIds": [
+ 585958
+ ],
+ "aliasedIds": [
+ "585958"
+ ],
+ "columnAlias": "ENCOUNTERRECORDNUMBER_HYOWJ",
+ "calculation": "count(distinct [585958])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": []
+ }
+ ]
+ }
+ ],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "8W28A",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 527101,
+ "sqlColumnName": "CALENDARDATETIME_QBAR8",
+ "columnType": "date",
+ "values": [
+ "01/{startDay}/2020",
+ "01/{endDay}/2021"
+ ],
+ "conditions": [],
+ "filterType": "calendar",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 527101
+ ]
+ }
+ ]
+ }
+ ],
+ "withTotals": false,
+ "rowLimit": 5000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/ChargesByServiceLine.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/ChargesByServiceLine.json
new file mode 100644
index 0000000..766c325
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/ChargesByServiceLine.json
@@ -0,0 +1,114 @@
+{
+ "rows": [
+ {
+ "dataColumnId": 539392,
+ "columnAlias": "FISCALQUARTER_4Z4AO",
+ "sortDirection": "asc"
+ },
+ {
+ "dataColumnId": 575222,
+ "columnAlias": "ROLLUP_QO8WQ",
+ "sortDirection": "asc"
+ }
+ ],
+ "values": [
+ {
+ "dataColumnId": 586855,
+ "calculationDataColumnIds": [
+ 586855
+ ],
+ "aliasedIds": [
+ "586855"
+ ],
+ "columnAlias": "TOTALCHARGES_EBYJD",
+ "calculation": "sum([586855])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": []
+ }
+ ]
+ }
+ ],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "5G66H",
+ "dataMartId": 1307,
+ "name": "Fiscal Year",
+ "dataColumnName": "Fiscal Year",
+ "dataColumnId": 539396,
+ "sqlColumnName": "FISCALYEAR_3U8QR",
+ "columnType": "numeric",
+ "values": [
+ 2019,
+ 2020
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 539396
+ ]
+ },
+ {
+ "id": "N6UI1",
+ "dataMartId": 1307,
+ "name": "Rollup",
+ "dataColumnName": "Rollup",
+ "dataColumnId": 575222,
+ "sqlColumnName": "ROLLUP_WC7SC",
+ "columnType": "text",
+ "values": [
+ "Orthopedics"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": []
+ },
+ {
+ "id": "8W28A",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 527101,
+ "sqlColumnName": "CALENDARDATETIME_QBAR8",
+ "columnType": "date",
+ "values": [
+ "01/{startDay}/2020",
+ "01/{endDay}/2021"
+ ],
+ "conditions": [],
+ "filterType": "calendar",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 527101
+ ]
+ }
+ ]
+ }
+ ],
+ "withTotals": false,
+ "rowLimit": 5000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/FixedDirectCostByYear.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/FixedDirectCostByYear.json
new file mode 100644
index 0000000..c13e19a
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/FixedDirectCostByYear.json
@@ -0,0 +1,88 @@
+{
+ "rows": [
+ {
+ "dataColumnId": 539377,
+ "columnAlias": "CALENDARDATETIME_4L6JT",
+ "datePart": "year",
+ "sortDirection": "asc"
+ }
+ ],
+ "values": [
+ {
+ "dataColumnId": 586664,
+ "calculationDataColumnIds": [
+ 586664
+ ],
+ "aliasedIds": [
+ "586664"
+ ],
+ "columnAlias": "FIXEDDIRECTCOST_FU1CS",
+ "calculation": "sum([586664])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": []
+ }
+ ]
+ }
+ ],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "M8X9O",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_FGBJE",
+ "columnType": "date",
+ "values": [
+ "2019",
+ "2020"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 539377
+ ]
+ },
+ {
+ "id": "8W28A",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 527101,
+ "sqlColumnName": "CALENDARDATETIME_QBAR8",
+ "columnType": "date",
+ "values": [
+ "01/{startDay}/2020",
+ "01/{endDay}/2021"
+ ],
+ "conditions": [],
+ "filterType": "calendar",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 527101
+ ]
+ }
+ ]
+ }
+ ],
+ "withTotals": false,
+ "rowLimit": 5000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/ServiceLineActualVsExpected.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/ServiceLineActualVsExpected.json
new file mode 100644
index 0000000..28cbeaf
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/ServiceLineActualVsExpected.json
@@ -0,0 +1,79 @@
+{
+ "rows": [
+ {
+ "dataColumnId": 575222,
+ "columnAlias": "ROLLUP_2UUGY",
+ "sortDirection": "asc"
+ }
+ ],
+ "values": [
+ {
+ "dataColumnId": 586854,
+ "calculationDataColumnIds": [
+ 586854
+ ],
+ "aliasedIds": [
+ "586854"
+ ],
+ "columnAlias": "TOTALACTUALPAYMENTS_GGKTJ",
+ "calculation": "sum([586854])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": []
+ }
+ ]
+ },
+ {
+ "dataColumnId": 586652,
+ "calculationDataColumnIds": [
+ 586652
+ ],
+ "aliasedIds": [
+ "586652"
+ ],
+ "columnAlias": "EXPECTEDPAYMENT_1L8Q4",
+ "calculation": "sum([586652])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": []
+ }
+ ]
+ }
+ ],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "8W28A",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 527101,
+ "sqlColumnName": "CALENDARDATETIME_QBAR8",
+ "columnType": "date",
+ "values": [
+ "01/{startDay}/2020",
+ "01/{endDay}/2021"
+ ],
+ "conditions": [],
+ "filterType": "calendar",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 527101
+ ]
+ }
+ ]
+ }
+ ],
+ "withTotals": false,
+ "rowLimit": 5000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/YOYCM.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/YOYCM.json
new file mode 100644
index 0000000..91a4925
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/YOYCM.json
@@ -0,0 +1,131 @@
+{
+ "rows": [],
+ "values": [
+ {
+ "dataColumnId": 586760,
+ "calculationDataColumnIds": [
+ 586760,
+ 586883
+ ],
+ "aliasedIds": [
+ "586760",
+ "586883"
+ ],
+ "columnAlias": "_H9VJX",
+ "calculation": "sum([586760]) - sum([586883])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "9WGYT",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_NYCXU",
+ "columnType": "date",
+ "values": [
+ "2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586760",
+ "586883"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "dataColumnId": 586760,
+ "calculationDataColumnIds": [
+ 586760,
+ 586883
+ ],
+ "aliasedIds": [
+ "586760",
+ "586883"
+ ],
+ "columnAlias": "_WB167",
+ "calculation": "sum([586760]) - sum([586883])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "9WGYT",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_NYCXU",
+ "columnType": "date",
+ "values": [
+ "2020"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586760",
+ "586883"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "8W28A",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 527101,
+ "sqlColumnName": "CALENDARDATETIME_QBAR8",
+ "columnType": "date",
+ "values": [
+ "01/{startDay}/2020",
+ "01/{endDay}/2021"
+ ],
+ "conditions": [],
+ "filterType": "calendar",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 527101
+ ]
+ }
+ ]
+ }
+ ],
+ "withTotals": false,
+ "rowLimit": 5000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/YOYNetRevenue.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/YOYNetRevenue.json
new file mode 100644
index 0000000..4608d2c
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/YOYNetRevenue.json
@@ -0,0 +1,125 @@
+{
+ "rows": [],
+ "values": [
+ {
+ "dataColumnId": 586760,
+ "calculationDataColumnIds": [
+ 586760
+ ],
+ "aliasedIds": [
+ "586760"
+ ],
+ "columnAlias": "NETREVENUE_ACT3M",
+ "calculation": "sum([586760])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "BPQ9S",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_ZWIUU",
+ "columnType": "date",
+ "values": [
+ "2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586760"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "dataColumnId": 586760,
+ "calculationDataColumnIds": [
+ 586760
+ ],
+ "aliasedIds": [
+ "586760"
+ ],
+ "columnAlias": "NETREVENUE_GFW12",
+ "calculation": "sum([586760])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "BPQ9S",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_ZWIUU",
+ "columnType": "date",
+ "values": [
+ "2020"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586760"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "8W28A",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 527101,
+ "sqlColumnName": "CALENDARDATETIME_QBAR8",
+ "columnType": "date",
+ "values": [
+ "01/{startDay}/2020",
+ "01/{endDay}/2021"
+ ],
+ "conditions": [],
+ "filterType": "calendar",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 527101
+ ]
+ }
+ ]
+ }
+ ],
+ "withTotals": false,
+ "rowLimit": 5000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/YOYTotalCosts.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/YOYTotalCosts.json
new file mode 100644
index 0000000..7e1a773
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/OverviewDashboard/YOYTotalCosts.json
@@ -0,0 +1,125 @@
+{
+ "rows": [],
+ "values": [
+ {
+ "dataColumnId": 586856,
+ "calculationDataColumnIds": [
+ 586856
+ ],
+ "aliasedIds": [
+ "586856"
+ ],
+ "columnAlias": "TOTALCOSTS_WMV34",
+ "calculation": "sum([586856])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "SH2MV",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_35LQ8",
+ "columnType": "date",
+ "values": [
+ "2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586856"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "dataColumnId": 586856,
+ "calculationDataColumnIds": [
+ 586856
+ ],
+ "aliasedIds": [
+ "586856"
+ ],
+ "columnAlias": "TOTALCOSTS_2QR2B",
+ "calculation": "sum([586856])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "K3JEA",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_H98A6",
+ "columnType": "date",
+ "values": [
+ "2020"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586856"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "8W28A",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 527101,
+ "sqlColumnName": "CALENDARDATETIME_QBAR8",
+ "columnType": "date",
+ "values": [
+ "01/{startDay}/2020",
+ "01/{endDay}/2021"
+ ],
+ "conditions": [],
+ "filterType": "calendar",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 527101
+ ]
+ }
+ ]
+ }
+ ],
+ "withTotals": false,
+ "rowLimit": 5000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/PatientTypeValidation.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/PatientTypeValidation.json
new file mode 100644
index 0000000..4b290cd
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/PatientTypeValidation.json
@@ -0,0 +1,107 @@
+{
+ "rows": [
+ {
+ "name": "Patient Type",
+ "dataColumnId": 564543,
+ "columnAlias": "NAME_OIAZS",
+ "sortDirection": "asc"
+ }
+ ],
+ "values": [
+ {
+ "name": "Cases",
+ "dataColumnId": 585958,
+ "calculationDataColumnIds": [
+ 585958
+ ],
+ "aliasedIds": [
+ "585958"
+ ],
+ "columnAlias": "VALUE_ENCOUNTERRECORDNUMBER_JQT6A_0",
+ "groupId": "IFZI4",
+ "valueId": "57G4J",
+ "calculation": "count(distinct [585958])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": []
+ }
+ ]
+ },
+ {
+ "name": "Charge",
+ "dataColumnId": 585887,
+ "calculationDataColumnIds": [
+ 585887
+ ],
+ "aliasedIds": [
+ "585887"
+ ],
+ "columnAlias": "VALUE_CHARGE_8HOLV_1",
+ "groupId": "IFZI4",
+ "valueId": "U91AZ",
+ "calculation": "sum([585887])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": []
+ }
+ ]
+ },
+ {
+ "name": "Length Of Stay",
+ "dataColumnId": 586742,
+ "calculationDataColumnIds": [
+ 586742
+ ],
+ "aliasedIds": [
+ "586742"
+ ],
+ "columnAlias": "VALUE_LENGTHOFSTAY_HI8CM_2",
+ "groupId": "IFZI4",
+ "valueId": "9B3XA",
+ "calculation": "sum([586742])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": []
+ }
+ ]
+ }
+ ],
+ "warnings": [],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "UDEBG",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_N9GBW",
+ "columnType": "date",
+ "values": [
+ "01/{startDay}/2020",
+ "01/{endDay}/2021"
+ ],
+ "conditions": [],
+ "filterType": "calendar",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 539377
+ ]
+ }
+ ]
+ }
+ ],
+ "withTotals": true,
+ "rowLimit": 10000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/ServiceLineYOY.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/ServiceLineYOY.json
new file mode 100644
index 0000000..4ae79c9
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/ServiceLineYOY.json
@@ -0,0 +1,329 @@
+{
+ "rows": [
+ {
+ "name": "Rollup",
+ "dataColumnId": 575222,
+ "columnAlias": "ROLLUP_AOO3Z",
+ "sortDirection": "asc"
+ }
+ ],
+ "values": [
+ {
+ "name": "2019_Cases",
+ "dataColumnId": 585958,
+ "calculationDataColumnIds": [
+ 585958
+ ],
+ "aliasedIds": [
+ "585958"
+ ],
+ "columnAlias": "PIVOT_ENCOUNTERRECORDNUMBER_265SK_0",
+ "groupValueIndex": 0,
+ "groupId": "FS9QQ",
+ "valueId": "PZTGR",
+ "calculation": "count(distinct [585958])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "K8JW6",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_VRHW7",
+ "columnType": "date",
+ "values": [
+ "2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "585958"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "2020_Cases",
+ "dataColumnId": 585958,
+ "calculationDataColumnIds": [
+ 585958
+ ],
+ "aliasedIds": [
+ "585958"
+ ],
+ "columnAlias": "PIVOT_ENCOUNTERRECORDNUMBER_265SK_1",
+ "groupValueIndex": 1,
+ "groupId": "FS9QQ",
+ "valueId": "PZTGR",
+ "calculation": "count(distinct [585958])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "K8JW6",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_VRHW7",
+ "columnType": "date",
+ "values": [
+ "2020"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "585958"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "2019_Total Charges",
+ "dataColumnId": 586855,
+ "calculationDataColumnIds": [
+ 586855
+ ],
+ "aliasedIds": [
+ "586855"
+ ],
+ "columnAlias": "PIVOT_TOTALCHARGES_48H1J_0",
+ "groupValueIndex": 0,
+ "groupId": "FS9QQ",
+ "valueId": "D8RZO",
+ "calculation": "sum([586855])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "K8JW6",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_VRHW7",
+ "columnType": "date",
+ "values": [
+ "2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586855"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "2020_Total Charges",
+ "dataColumnId": 586855,
+ "calculationDataColumnIds": [
+ 586855
+ ],
+ "aliasedIds": [
+ "586855"
+ ],
+ "columnAlias": "PIVOT_TOTALCHARGES_48H1J_1",
+ "groupValueIndex": 1,
+ "groupId": "FS9QQ",
+ "valueId": "D8RZO",
+ "calculation": "sum([586855])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "K8JW6",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_VRHW7",
+ "columnType": "date",
+ "values": [
+ "2020"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586855"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "2019_Total Costs",
+ "dataColumnId": 586856,
+ "calculationDataColumnIds": [
+ 586856
+ ],
+ "aliasedIds": [
+ "586856"
+ ],
+ "columnAlias": "PIVOT_TOTALCOSTS_LPFBQ_0",
+ "groupValueIndex": 0,
+ "groupId": "FS9QQ",
+ "valueId": "K5GVD",
+ "calculation": "sum([586856])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "K8JW6",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_VRHW7",
+ "columnType": "date",
+ "values": [
+ "2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586856"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "2020_Total Costs",
+ "dataColumnId": 586856,
+ "calculationDataColumnIds": [
+ 586856
+ ],
+ "aliasedIds": [
+ "586856"
+ ],
+ "columnAlias": "PIVOT_TOTALCOSTS_LPFBQ_1",
+ "groupValueIndex": 1,
+ "groupId": "FS9QQ",
+ "valueId": "K5GVD",
+ "calculation": "sum([586856])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "K8JW6",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_VRHW7",
+ "columnType": "date",
+ "values": [
+ "2020"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586856"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "warnings": [],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "N1Q3F",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_AHA9P",
+ "columnType": "date",
+ "values": [
+ "01/{startDay}/2018",
+ "01/{endDay}/2020"
+ ],
+ "conditions": [],
+ "filterType": "calendar",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 539377
+ ]
+ }
+ ]
+ }
+ ],
+ "withTotals": true,
+ "rowLimit": 10000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/BestPracticeWorkbook/YOYPatientType.json b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/YOYPatientType.json
new file mode 100644
index 0000000..2b47ed1
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/BestPracticeWorkbook/YOYPatientType.json
@@ -0,0 +1,329 @@
+{
+ "rows": [
+ {
+ "name": "Patient Type",
+ "dataColumnId": 564543,
+ "columnAlias": "NAME_OXV4H",
+ "sortDirection": "asc"
+ }
+ ],
+ "values": [
+ {
+ "name": "2019_Total Charges",
+ "dataColumnId": 586855,
+ "calculationDataColumnIds": [
+ 586855
+ ],
+ "aliasedIds": [
+ "586855"
+ ],
+ "columnAlias": "PIVOT_TOTALCHARGES_RK2QL_0",
+ "groupValueIndex": 0,
+ "groupId": "NNXYG",
+ "valueId": "ES7QJ",
+ "calculation": "sum([586855])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "GFETD",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_ZIC1I",
+ "columnType": "date",
+ "values": [
+ "2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586855"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "2020_Total Charges",
+ "dataColumnId": 586855,
+ "calculationDataColumnIds": [
+ 586855
+ ],
+ "aliasedIds": [
+ "586855"
+ ],
+ "columnAlias": "PIVOT_TOTALCHARGES_RK2QL_1",
+ "groupValueIndex": 1,
+ "groupId": "NNXYG",
+ "valueId": "ES7QJ",
+ "calculation": "sum([586855])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "GFETD",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_ZIC1I",
+ "columnType": "date",
+ "values": [
+ "2020"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586855"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "2019_Total Costs",
+ "dataColumnId": 586856,
+ "calculationDataColumnIds": [
+ 586856
+ ],
+ "aliasedIds": [
+ "586856"
+ ],
+ "columnAlias": "PIVOT_TOTALCOSTS_NIT3O_0",
+ "groupValueIndex": 0,
+ "groupId": "NNXYG",
+ "valueId": "F6OR3",
+ "calculation": "sum([586856])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "GFETD",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_ZIC1I",
+ "columnType": "date",
+ "values": [
+ "2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586856"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "2020_Total Costs",
+ "dataColumnId": 586856,
+ "calculationDataColumnIds": [
+ 586856
+ ],
+ "aliasedIds": [
+ "586856"
+ ],
+ "columnAlias": "PIVOT_TOTALCOSTS_NIT3O_1",
+ "groupValueIndex": 1,
+ "groupId": "NNXYG",
+ "valueId": "F6OR3",
+ "calculation": "sum([586856])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "GFETD",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_ZIC1I",
+ "columnType": "date",
+ "values": [
+ "2020"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586856"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "2019_Total Actual Payments",
+ "dataColumnId": 586854,
+ "calculationDataColumnIds": [
+ 586854
+ ],
+ "aliasedIds": [
+ "586854"
+ ],
+ "columnAlias": "PIVOT_TOTALACTUALPAYMENTS_O29XX_0",
+ "groupValueIndex": 0,
+ "groupId": "NNXYG",
+ "valueId": "YAE7N",
+ "calculation": "sum([586854])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "GFETD",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_ZIC1I",
+ "columnType": "date",
+ "values": [
+ "2019"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586854"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "2020_Total Actual Payments",
+ "dataColumnId": 586854,
+ "calculationDataColumnIds": [
+ 586854
+ ],
+ "aliasedIds": [
+ "586854"
+ ],
+ "columnAlias": "PIVOT_TOTALACTUALPAYMENTS_O29XX_1",
+ "groupValueIndex": 1,
+ "groupId": "NNXYG",
+ "valueId": "YAE7N",
+ "calculation": "sum([586854])",
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "GFETD",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_ZIC1I",
+ "columnType": "date",
+ "values": [
+ "2020"
+ ],
+ "conditions": [],
+ "filterType": "list",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [],
+ "aliasedDataColumnIds": [
+ "586854"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "warnings": [],
+ "filterGroups": [
+ {
+ "logicalOperator": "and",
+ "filters": [
+ {
+ "id": "221UQ",
+ "dataMartId": 1307,
+ "name": "Calendar Date Time",
+ "dataColumnName": "Calendar Date Time",
+ "dataColumnId": 539377,
+ "sqlColumnName": "CALENDARDATETIME_MD2OV",
+ "columnType": "date",
+ "values": [
+ "01/{startDay}/2018",
+ "01/{endDay}/2020"
+ ],
+ "conditions": [],
+ "filterType": "calendar",
+ "listUseNotIn": false,
+ "datePart": "year",
+ "dateTimeFrameOffset": 0,
+ "dateTimeFrameOffsetDisplay": 0,
+ "dateTimeFrameCount": 0,
+ "dateTimeFrameCountDisplay": 0,
+ "isCustom": false,
+ "relatedDataColumnIds": [
+ 539377
+ ]
+ }
+ ]
+ }
+ ],
+ "withTotals": true,
+ "rowLimit": 10000
+}
\ No newline at end of file
diff --git a/loadTest/docker/testRunner/tests/PivotERN.json b/loadTest/docker/testRunner/tests/PivotERN.json
new file mode 100644
index 0000000..2322942
--- /dev/null
+++ b/loadTest/docker/testRunner/tests/PivotERN.json
@@ -0,0 +1 @@
+{"rows":[{"name":"Rollup","dataColumnId":575222,"columnAlias":"ROLLUP_AOO3Z","sortDirection":"asc"}],"values":[{"name":"2019_Cases","dataColumnId":585958,"calculationDataColumnIds":[585958],"aliasedIds":["585958"],"columnAlias":"PIVOT_ENCOUNTERRECORDNUMBER_265SK_0","groupValueIndex":0,"groupId":"FS9QQ","valueId":"PZTGR","calculation":"count(distinct [585958])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"K8JW6","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_VRHW7","columnType":"date","values":["2019"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["585958"]}]}]},{"name":"2020_Cases","dataColumnId":585958,"calculationDataColumnIds":[585958],"aliasedIds":["585958"],"columnAlias":"PIVOT_ENCOUNTERRECORDNUMBER_265SK_1","groupValueIndex":1,"groupId":"FS9QQ","valueId":"PZTGR","calculation":"count(distinct [585958])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"K8JW6","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_VRHW7","columnType":"date","values":["2020"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["585958"]}]}]},{"name":"2019_Total Charges","dataColumnId":586855,"calculationDataColumnIds":[586855],"aliasedIds":["586855"],"columnAlias":"PIVOT_TOTALCHARGES_48H1J_0","groupValueIndex":0,"groupId":"FS9QQ","valueId":"D8RZO","calculation":"sum([586855])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"K8JW6","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_VRHW7","columnType":"date","values":["2019"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586855"]}]}]},{"name":"2020_Total Charges","dataColumnId":586855,"calculationDataColumnIds":[586855],"aliasedIds":["586855"],"columnAlias":"PIVOT_TOTALCHARGES_48H1J_1","groupValueIndex":1,"groupId":"FS9QQ","valueId":"D8RZO","calculation":"sum([586855])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"K8JW6","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_VRHW7","columnType":"date","values":["2020"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586855"]}]}]},{"name":"2019_Total Costs","dataColumnId":586856,"calculationDataColumnIds":[586856],"aliasedIds":["586856"],"columnAlias":"PIVOT_TOTALCOSTS_LPFBQ_0","groupValueIndex":0,"groupId":"FS9QQ","valueId":"K5GVD","calculation":"sum([586856])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"K8JW6","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_VRHW7","columnType":"date","values":["2019"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586856"]}]}]},{"name":"2020_Total Costs","dataColumnId":586856,"calculationDataColumnIds":[586856],"aliasedIds":["586856"],"columnAlias":"PIVOT_TOTALCOSTS_LPFBQ_1","groupValueIndex":1,"groupId":"FS9QQ","valueId":"K5GVD","calculation":"sum([586856])","filterGroups":[{"logicalOperator":"and","filters":[{"id":"K8JW6","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_VRHW7","columnType":"date","values":["2020"],"conditions":[],"filterType":"list","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[],"aliasedDataColumnIds":["586856"]}]}]}],"warnings":[],"filterGroups":[{"logicalOperator":"and","filters":[{"id":"N1Q3F","dataMartId":1307,"name":"Calendar Date Time","dataColumnName":"Calendar Date Time","dataColumnId":539377,"sqlColumnName":"CALENDARDATETIME_AHA9P","columnType":"date","values":["01/{startDay}/2018","01/{endDay}/2020"],"conditions":[],"filterType":"calendar","listUseNotIn":false,"datePart":"year","dateTimeFrameOffset":0,"dateTimeFrameOffsetDisplay":0,"dateTimeFrameCount":0,"dateTimeFrameCountDisplay":0,"isCustom":false,"relatedDataColumnIds":[539377]}]}],"withTotals":true,"rowLimit":10000}
\ No newline at end of file
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 0000000..3bf3f7b
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/sonarsuppressions.xml b/sonarsuppressions.xml
new file mode 100644
index 0000000..6f4969c
--- /dev/null
+++ b/sonarsuppressions.xml
@@ -0,0 +1,151 @@
+
+
+
+
+ ^pkg:generic/TeamCity\.ServiceMessages@.*$
+ cpe:/a:jetbrains:teamcity
+ CVE-2014-10002
+
+
+
+ ^pkg:generic/Microsoft\.AspNetCore\.Authentication\.JwtBearer@.*$
+ CVE-2020-1108
+
+
+
+ ^pkg:generic/Microsoft\.VisualStudio\.CodeCoverage\.Shim@.*$
+ CVE-2020-1171
+
+
+
+ ^pkg:generic/Microsoft\.VisualStudio\.CodeCoverage\.Shim@.*$
+ CVE-2020-1192
+
+
+
+ ^pkg:generic/SonarScanner\.MSBuild\.Tasks@.*$
+ CVE-2020-22475
+
+
+
+ ^pkg:npm/browserslist@.*$
+ 1747
+
+
+
+ ^pkg:npm/css\-what@.*$
+ 1754
+
+
+
+ ^pkg:npm/dns\-packet@.*$
+ 1745
+
+
+
+ ^pkg:npm/normalize\-url@.*$
+ 1755
+
+
+
+ ^pkg:npm/trim\-newlines@.*$
+ 1753
+
+
+
+ ^pkg:generic/TeamCity\.ServiceMessages@.*$
+ CVE-2014-10036
+
+
+
+ ^pkg:generic/TeamCity\.ServiceMessages@.*$
+ CVE-2019-12156
+
+
+
+ ^pkg:generic/TeamCity\.ServiceMessages@.*$
+ CVE-2019-12157
+
+
+
+ ^pkg:generic/TeamCity\.ServiceMessages@.*$
+ CVE-2019-12841
+
+
+
+ ^pkg:generic/TeamCity\.ServiceMessages@.*$
+ CVE-2019-12842
+
+
+
+ ^pkg:generic/TeamCity\.ServiceMessages@.*$
+ CVE-2019-12843
+
+
+
+ ^pkg:generic/TeamCity\.ServiceMessages@.*$
+ CVE-2019-12844
+
+
+
+ ^pkg:generic/TeamCity\.ServiceMessages@.*$
+ CVE-2019-12845
+
+
+
+ ^pkg:generic/TeamCity\.VSTest\.TestLogger@.*$
+ cpe:/a:jetbrains:teamcity
+
+
+
+ ^pkg:generic/TeamCity\.VSTest\.TestAdapter@.*$
+ cpe:/a:jetbrains:teamcity
+
+
+
+ ^pkg:javascript/jquery@.*$
+ Regex in its jQuery.htmlPrefilter sometimes may introduce XSS
+
+
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Authorization/AuthorizationOptions.cs b/src/Strata.Analytics.Api/Authorization/AuthorizationOptions.cs
new file mode 100644
index 0000000..26bed4a
--- /dev/null
+++ b/src/Strata.Analytics.Api/Authorization/AuthorizationOptions.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Strata.Analytics.Api.Authorization
+{
+ public class AuthorizationOptions
+ {
+ public Guid AuthorizedDbGuid { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Authorization/AuthorizedDbMinimumRoleHandler.cs b/src/Strata.Analytics.Api/Authorization/AuthorizedDbMinimumRoleHandler.cs
new file mode 100644
index 0000000..f02d194
--- /dev/null
+++ b/src/Strata.Analytics.Api/Authorization/AuthorizedDbMinimumRoleHandler.cs
@@ -0,0 +1,38 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using System;
+using System.Threading.Tasks;
+using Strata.CoreLib.Claims.Extensions;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Options;
+
+namespace Strata.Analytics.Api.Authorization
+{
+ public class AuthorizedDbMinimumRoleHandler : AuthorizationHandler
+ {
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly IWebHostEnvironment _env;
+ private Guid _authorizedDbGuid;
+
+ public AuthorizedDbMinimumRoleHandler(IHttpContextAccessor httpContextAccessor, IWebHostEnvironment env, IOptions options)
+ {
+ _httpContextAccessor = httpContextAccessor;
+ _env = env;
+ _authorizedDbGuid = options.Value.AuthorizedDbGuid;
+ }
+
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorizedDbMinimumRoleRequirement requirement)
+ {
+ var user = _httpContextAccessor.HttpContext.User;
+ var dbGuid = user.GetDatabaseGuid();
+ if (!_env.IsProduction() || dbGuid == _authorizedDbGuid)
+ {
+ context.Succeed(requirement);
+ return Task.CompletedTask;
+ }
+ context.Fail();
+ return Task.CompletedTask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Authorization/AuthorizedDbMinimumRoleRequirement.cs b/src/Strata.Analytics.Api/Authorization/AuthorizedDbMinimumRoleRequirement.cs
new file mode 100644
index 0000000..8230ace
--- /dev/null
+++ b/src/Strata.Analytics.Api/Authorization/AuthorizedDbMinimumRoleRequirement.cs
@@ -0,0 +1,8 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Strata.Analytics.Api.Authorization
+{
+ public class AuthorizedDbMinimumRoleRequirement : IAuthorizationRequirement
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Authorization/CustomSQLStrataOnlyRoleHandler.cs b/src/Strata.Analytics.Api/Authorization/CustomSQLStrataOnlyRoleHandler.cs
new file mode 100644
index 0000000..fbc163b
--- /dev/null
+++ b/src/Strata.Analytics.Api/Authorization/CustomSQLStrataOnlyRoleHandler.cs
@@ -0,0 +1,52 @@
+
+using Microsoft.AspNetCore.Authorization;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Strata.Analytics.Biz.Extracts.Services;
+using Strata.Analytics.Biz.Extracts.Models;
+using Strata.CoreLib.Claims.Extensions;
+
+
+namespace Strata.Analytics.Api.Authorization
+{
+ public class CustomSQLStrataOnlyRoleHandler : AuthorizationHandler
+ {
+ private readonly IExtractService _extractService;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ public CustomSQLStrataOnlyRoleHandler(IExtractService extractService, IHttpContextAccessor httpContextAccessor)
+ {
+ _extractService = extractService;
+ _httpContextAccessor = httpContextAccessor;
+ }
+
+ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ExtractCustomSqlStrataOnlyRoleRequirement requirement)
+ {
+ var extractIdObj = _httpContextAccessor.HttpContext!.GetRouteData()?.Values["extractId"];
+ if (extractIdObj == null)
+ {
+ context.Fail();
+ return;
+ }
+
+ var extractId = int.Parse(extractIdObj.ToString()!);
+
+ var extract =
+ await _extractService.GetByIdAsync(extractId, System.Threading.CancellationToken.None);
+
+ if (extract == null)
+ {
+ context.Fail();
+ return;
+ }
+
+ if (extract.ExtractType == ExtractType.CustomSql && !context.User.IsSdtEmployee())
+ {
+ context.Fail();
+ return;
+ }
+ context.Succeed(requirement);
+ return;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Authorization/DataSourceMinimumRoleHandler.cs b/src/Strata.Analytics.Api/Authorization/DataSourceMinimumRoleHandler.cs
new file mode 100644
index 0000000..5d7aa70
--- /dev/null
+++ b/src/Strata.Analytics.Api/Authorization/DataSourceMinimumRoleHandler.cs
@@ -0,0 +1,92 @@
+using Microsoft.AspNetCore.Authorization;
+using System.Threading.Tasks;
+using Strata.Analytics.Biz.DataSources.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Strata.CoreLib.Claims.Extensions;
+using System;
+using System.Collections.Generic;
+
+namespace Strata.Analytics.Api.Authorization
+{
+ public class DataSourceMinimumRoleHandler : AuthorizationHandler
+ {
+ private readonly IDataSourceService _dataSourceService;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ public DataSourceMinimumRoleHandler(IDataSourceService dataSourceService, IHttpContextAccessor httpContextAccessor)
+ {
+ _dataSourceService = dataSourceService;
+ _httpContextAccessor = httpContextAccessor;
+ }
+
+ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, DataSourceMinimumRoleRequirement requirement)
+ {
+ var dataSourceId = await GetDataSourceId(context);
+ if (!dataSourceId.HasValue)
+ {
+ context.Fail();
+ return;
+ }
+
+ var dataSourceRole = await _dataSourceService.GetCurrentUserRoleAsync(dataSourceId.Value, System.Threading.CancellationToken.None);
+
+ if (!dataSourceRole.HasValue || dataSourceRole.Value < requirement.MinimumRole)
+ {
+ context.Fail();
+ return;
+ }
+
+ context.Succeed(requirement);
+ }
+
+ private async Task GetDataSourceId(AuthorizationHandlerContext context)
+ {
+ // Check route for dataSourceId
+ var dataSourceIdObj = _httpContextAccessor.HttpContext.GetRouteData()?.Values["dataSourceId"];
+ if (dataSourceIdObj == null)
+ {
+ // Check body for dataSourceId
+ var req = _httpContextAccessor.HttpContext.Request;
+
+ try
+ {
+ req.EnableBuffering();
+ req.Body.Position = 0;
+ var body = await req.ReadFromJsonAsync>(null);
+ req.Body.Position = 0;
+ if (req.Body.Length > 0)
+ {
+ foreach (var kvp in body)
+ if (string.Equals(kvp.Key, "dataSourceId", StringComparison.InvariantCultureIgnoreCase))
+ dataSourceIdObj = kvp.Value;
+ }
+ else
+ return null;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ if (!int.TryParse(dataSourceIdObj.ToString(), out int dataSourceId))
+ return null;
+
+ return dataSourceId;
+ }
+
+ // Swallows throw when user does not have SdtEmployee claim and returns false
+ private bool IsSdtEmployee(AuthorizationHandlerContext context)
+ {
+ var isSdtEmployee = false;
+ try
+ {
+ isSdtEmployee = context.User.IsSdtEmployee();
+ }
+ catch { }
+
+ return isSdtEmployee;
+ }
+ }
+}
diff --git a/src/Strata.Analytics.Api/Authorization/DataSourceMinimumRoleRequirement.cs b/src/Strata.Analytics.Api/Authorization/DataSourceMinimumRoleRequirement.cs
new file mode 100644
index 0000000..fc8c3c7
--- /dev/null
+++ b/src/Strata.Analytics.Api/Authorization/DataSourceMinimumRoleRequirement.cs
@@ -0,0 +1,16 @@
+using Microsoft.AspNetCore.Authorization;
+using Strata.Analytics.Biz.DataSources.Models;
+
+namespace Strata.Analytics.Api.Authorization
+{
+ public class DataSourceMinimumRoleRequirement : IAuthorizationRequirement
+ {
+ public DataSourceRole MinimumRole { get; set; }
+
+ public DataSourceMinimumRoleRequirement(DataSourceRole role)
+ {
+ MinimumRole = role;
+ }
+
+ }
+}
diff --git a/src/Strata.Analytics.Api/Authorization/ExtractCustomSQLStrataOnlyRoleRequirement.cs b/src/Strata.Analytics.Api/Authorization/ExtractCustomSQLStrataOnlyRoleRequirement.cs
new file mode 100644
index 0000000..c290fb7
--- /dev/null
+++ b/src/Strata.Analytics.Api/Authorization/ExtractCustomSQLStrataOnlyRoleRequirement.cs
@@ -0,0 +1,10 @@
+using Microsoft.AspNetCore.Authorization;
+
+public class ExtractCustomSqlStrataOnlyRoleRequirement : IAuthorizationRequirement
+{
+
+ public ExtractCustomSqlStrataOnlyRoleRequirement()
+ {
+ }
+
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Authorization/ExtractMinimumRoleHandler.cs b/src/Strata.Analytics.Api/Authorization/ExtractMinimumRoleHandler.cs
new file mode 100644
index 0000000..8752f51
--- /dev/null
+++ b/src/Strata.Analytics.Api/Authorization/ExtractMinimumRoleHandler.cs
@@ -0,0 +1,47 @@
+using Microsoft.AspNetCore.Authorization;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Strata.Analytics.Biz.Extracts.Services;
+
+namespace Strata.Analytics.Api.Authorization
+{
+ public class ExtractMinimumRoleHandler : AuthorizationHandler
+ {
+ private readonly IExtractService _extractService;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ public ExtractMinimumRoleHandler(IExtractService extractService, IHttpContextAccessor httpContextAccessor)
+ {
+ _extractService = extractService;
+ _httpContextAccessor = httpContextAccessor;
+ }
+ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ExtractMinimumRoleRequirement requirement)
+ {
+ var extractIdObj = _httpContextAccessor.HttpContext!.GetRouteData()?.Values["extractId"];
+ if (extractIdObj == null)
+ {
+ context.Fail();
+ return;
+ }
+
+ var extractId = int.Parse(extractIdObj.ToString()!);
+ var extractRole = await _extractService.GetCurrentUserRoleAsync(extractId, System.Threading.CancellationToken.None);
+
+ var extract =
+ await _extractService.GetByIdAsync(extractId, System.Threading.CancellationToken.None);
+ if (extract == null)
+ {
+ context.Fail();
+ return;
+ }
+
+
+ if (!extractRole.HasValue || extractRole.Value < requirement.MinimumRole)
+ {
+ context.Fail();
+ return;
+ }
+ context.Succeed(requirement);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Authorization/ExtractMinimumRoleRequirement.cs b/src/Strata.Analytics.Api/Authorization/ExtractMinimumRoleRequirement.cs
new file mode 100644
index 0000000..b09d592
--- /dev/null
+++ b/src/Strata.Analytics.Api/Authorization/ExtractMinimumRoleRequirement.cs
@@ -0,0 +1,16 @@
+using Microsoft.AspNetCore.Authorization;
+using Strata.Analytics.Biz.Extracts.Models;
+
+namespace Strata.Analytics.Api.Authorization
+{
+ public class ExtractMinimumRoleRequirement : IAuthorizationRequirement
+ {
+ public ExtractRole MinimumRole { get; set; }
+
+ public ExtractMinimumRoleRequirement(ExtractRole role)
+ {
+ MinimumRole = role;
+ }
+
+ }
+}
diff --git a/src/Strata.Analytics.Api/Authorization/Policies.cs b/src/Strata.Analytics.Api/Authorization/Policies.cs
new file mode 100644
index 0000000..7e89b97
--- /dev/null
+++ b/src/Strata.Analytics.Api/Authorization/Policies.cs
@@ -0,0 +1,16 @@
+namespace Strata.Analytics.Api.Authorization
+{
+ public class Policies
+ {
+ public const string StrataOnly = "StrataOnly";
+ public const string IsAdmin = "IsAdmin";
+ public const string DataSourceViewer = "DataSourceViewer";
+ public const string DataSourceEditor = "DataSourceEditor";
+ public const string WorkbookViewer = "WorkbookViewer";
+ public const string WorkbookEditor = "WorkbookEditor";
+ public const string ExtractViewer = "ExtractViewer";
+ public const string ExtractEditor = "ExtractEditor";
+ public const string AuthorizedDbOnly = "AuthorizedDbOnly";
+ public const string CustomSqlAccess = "CustomSqlAccess";
+ }
+}
diff --git a/src/Strata.Analytics.Api/Authorization/WorkbookMinimumRoleHandler.cs b/src/Strata.Analytics.Api/Authorization/WorkbookMinimumRoleHandler.cs
new file mode 100644
index 0000000..f806d1f
--- /dev/null
+++ b/src/Strata.Analytics.Api/Authorization/WorkbookMinimumRoleHandler.cs
@@ -0,0 +1,42 @@
+using Microsoft.AspNetCore.Authorization;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Strata.Analytics.Biz.Workbooks.Services;
+
+namespace Strata.Analytics.Api.Authorization
+{
+ public class WorkbookMinimumRoleHandler : AuthorizationHandler
+ {
+ private readonly IWorkbookService _workbookService;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ public WorkbookMinimumRoleHandler(IWorkbookService workbookService, IHttpContextAccessor httpContextAccessor)
+ {
+ _workbookService = workbookService;
+ _httpContextAccessor = httpContextAccessor;;
+ }
+
+ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, WorkbookMinimumRoleRequirement requirement)
+ {
+ var workbookIdObj = _httpContextAccessor.HttpContext.GetRouteData()?.Values["workbookId"];
+ if (workbookIdObj == null)
+ {
+ context.Fail();
+ return;
+ }
+
+ var workbookId = int.Parse(workbookIdObj.ToString());
+ var workbookRole = await _workbookService.GetCurrentUserRoleAsync(workbookId, System.Threading.CancellationToken.None);
+
+ if (!workbookRole.HasValue || workbookRole.Value < requirement.MinimumRole)
+ {
+ context.Fail();
+ return;
+ }
+
+ context.Succeed(requirement);
+ }
+
+ }
+}
diff --git a/src/Strata.Analytics.Api/Authorization/WorkbookMinimumRoleRequirement.cs b/src/Strata.Analytics.Api/Authorization/WorkbookMinimumRoleRequirement.cs
new file mode 100644
index 0000000..81b5c56
--- /dev/null
+++ b/src/Strata.Analytics.Api/Authorization/WorkbookMinimumRoleRequirement.cs
@@ -0,0 +1,16 @@
+using Microsoft.AspNetCore.Authorization;
+using Strata.Analytics.Models.Workbooks;
+
+namespace Strata.Analytics.Api.Authorization
+{
+ public class WorkbookMinimumRoleRequirement : IAuthorizationRequirement
+ {
+ public WorkbookRole MinimumRole { get; set; }
+
+ public WorkbookMinimumRoleRequirement(WorkbookRole role)
+ {
+ MinimumRole = role;
+ }
+
+ }
+}
diff --git a/src/Strata.Analytics.Api/Controllers/ContentMigrationController.cs b/src/Strata.Analytics.Api/Controllers/ContentMigrationController.cs
new file mode 100644
index 0000000..ce899f4
--- /dev/null
+++ b/src/Strata.Analytics.Api/Controllers/ContentMigrationController.cs
@@ -0,0 +1,122 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Strata.Analytics.Biz.ContentMigration.Services;
+using Strata.Analytics.Biz.ContentMigration.Models;
+using Microsoft.AspNetCore.Http;
+using System;
+using Microsoft.AspNetCore.Authorization;
+using Strata.Analytics.Api.Authorization;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Strata.Analytics.Biz.ContentMigration.Models.MigrationPlan;
+
+namespace Strata.Analytics.Api.Controllers
+{
+ [ApiController]
+ [ApiVersion("1.0")]
+ [Route("api/v{api-version:apiVersion}/[controller]")]
+ public class ContentMigrationController : ControllerBase
+ {
+ private readonly IContentMigrationService _contentMigrationService;
+
+ public ContentMigrationController(IContentMigrationService contentMigrationService)
+ {
+ _contentMigrationService = contentMigrationService;
+ }
+
+ [HttpGet("legacy-reports")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task> GetAdHocReports(CancellationToken cancellationToken)
+ {
+ return await _contentMigrationService.GetAdHocReports(cancellationToken);
+ }
+
+ [HttpPost("{reportGuid}/migrate")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> MigrateReport([FromRoute] Guid reportGuid, [FromQuery, BindRequired] int workbookId, [FromQuery, BindRequired] int dataSourceId, [FromBody] Plan plan, CancellationToken cancellationToken)
+ {
+ var migratedReport = await _contentMigrationService.MigrateReport(reportGuid, workbookId, dataSourceId, plan, cancellationToken);
+ if (migratedReport == null)
+ {
+ return NotFound();
+ }
+ return Ok(migratedReport);
+ }
+
+ [HttpPost("{reportGuid}/migrateExtract")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> MigrateReportToExtract([FromRoute] Guid reportGuid, [FromQuery, BindRequired] int dataSourceId, [FromBody] Plan plan, CancellationToken cancellationToken)
+ {
+ var migratedReport = await _contentMigrationService.MigrateReportToExtract(reportGuid, dataSourceId, plan, cancellationToken);
+ if (migratedReport == null)
+ {
+ return NotFound();
+ }
+ return Ok(migratedReport);
+ }
+
+ [HttpGet("migrationPlan")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> GetMigrationPlan([FromQuery, BindRequired] Guid reportGuid, [FromQuery, BindRequired] int dataSourceId, CancellationToken cancellationToken)
+ {
+ var migrationPlan = await _contentMigrationService.GetMigrationPlan(reportGuid, dataSourceId, cancellationToken);
+ if (migrationPlan == null)
+ {
+ return NotFound();
+ }
+ return Ok(migrationPlan);
+ }
+
+ [HttpPut("dataMappings")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> UpdateDataMapping([FromBody] DataMapping dataMapping, CancellationToken cancellationToken)
+ {
+ var updatedDataMapping = await _contentMigrationService.UpdateDataMapping(dataMapping, cancellationToken);
+ if (updatedDataMapping == null)
+ {
+ return NotFound();
+ }
+ return Ok(updatedDataMapping);
+ }
+
+ [HttpGet("dataMappings")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task>> GetDataMappings(CancellationToken cancellationToken)
+ {
+ var dataMappings = await _contentMigrationService.GetDataMappings(cancellationToken);
+ return Ok(dataMappings);
+ }
+
+ [HttpPost("dataMappings")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ public async Task AddDataMappings([FromBody] IEnumerable dataMappings, CancellationToken cancellationToken)
+ {
+ await _contentMigrationService.AddDataMappings(dataMappings, cancellationToken);
+ return NoContent();
+ }
+
+ [HttpDelete("dataMappings/{dataMappingId}")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task DeleteDataMapping([FromRoute] int dataMappingId, CancellationToken cancellationToken)
+ {
+ var mapping = await _contentMigrationService.GetDataMapping(dataMappingId, cancellationToken);
+ if (mapping == null)
+ {
+ return NotFound();
+ }
+
+ await _contentMigrationService.DeleteDataMapping(mapping, cancellationToken);
+ return NoContent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Controllers/DataSourcesController.cs b/src/Strata.Analytics.Api/Controllers/DataSourcesController.cs
new file mode 100644
index 0000000..7a259f3
--- /dev/null
+++ b/src/Strata.Analytics.Api/Controllers/DataSourcesController.cs
@@ -0,0 +1,241 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Strata.Analytics.Api.Authorization;
+using Strata.Analytics.Biz.DataQuery;
+using Strata.Analytics.Biz.DataSources.Models;
+using Strata.Analytics.Biz.DataSources.Services;
+using Strata.Analytics.Biz.Export.Models;
+using Strata.Analytics.Biz.Export.Services;
+using Strata.Analytics.Models.Utils;
+using Strata.DataSchema.Models.Query;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Strata.Analytics.Api.Controllers
+{
+ [ApiController]
+ [ApiVersion("1.0")]
+ [Route("api/v{api-version:apiVersion}/[controller]")]
+ public class DataSourcesController : ControllerBase
+ {
+ private readonly IDataSourceService _dataSourceService;
+ private readonly IDataQueryService _dataQueryService;
+ private readonly IExportService _exportService;
+ private readonly ILogger _logger;
+
+ public DataSourcesController(IDataSourceService dataSourceService, IDataQueryService dataQueryService, IExportService exportService, ILogger logger)
+ {
+ _dataSourceService = dataSourceService;
+ _dataQueryService = dataQueryService;
+ _exportService = exportService;
+ _logger = logger;
+ }
+
+ [HttpGet]
+ public async Task> GetDataSourcesAsync(CancellationToken cancellationToken)
+ {
+
+ return await _dataSourceService.GetAllAsync(cancellationToken);
+ }
+
+ [HttpGet("{dataSourceId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> GetById(int dataSourceId, CancellationToken cancellationToken)
+ {
+ var dataSource = await _dataSourceService.GetByIdAsync(dataSourceId, cancellationToken);
+ if (dataSource == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(dataSource);
+ }
+
+ [HttpPut("{dataSourceId}")]
+ [Authorize(Policy = Policies.DataSourceEditor)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> Update(int dataSourceId, DataSourceSaveInfo dataSourceSaveInfo, CancellationToken cancellationToken)
+ {
+ var isFound = await _dataSourceService.ContainsAsync(dataSourceId, cancellationToken);
+ if (!isFound)
+ {
+ return NotFound();
+ }
+
+ var updatedDataSource = await _dataSourceService.UpdateAsync(dataSourceId, dataSourceSaveInfo, cancellationToken);
+
+ return Ok(updatedDataSource);
+ }
+
+ [HttpPut("")]
+ [Authorize(Policy = Policies.DataSourceEditor)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> Save(DataSourceSaveInfo dataSourceSaveInfo, CancellationToken cancellationToken)
+ {
+ var isFound = await _dataSourceService.ContainsAsync(dataSourceSaveInfo.DataSourceId, cancellationToken);
+ if (!isFound)
+ {
+ return NotFound();
+ }
+
+ var updatedDataSource = await _dataSourceService.SaveAsync(dataSourceSaveInfo, cancellationToken);
+
+ return Ok(updatedDataSource);
+ }
+
+ [HttpDelete("{dataSourceId}")]
+ [Authorize(Policy = Policies.DataSourceEditor)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task Delete(int dataSourceId, CancellationToken cancellationToken)
+ {
+ await _dataSourceService.DeleteAsync(dataSourceId, cancellationToken);
+ return NoContent();
+ }
+
+ [HttpPost("")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ public async Task> Create(DataSourceSaveInfo dataSourceSaveInfo, CancellationToken cancellationToken)
+ {
+ return await _dataSourceService.CreateAsync(dataSourceSaveInfo, cancellationToken);
+ }
+
+ [HttpPost("{dataSourceId}/tables/{dataTableId}")]
+ [Authorize(Policy = Policies.DataSourceEditor)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task> AddDataTableLink([FromRoute] int dataSourceId, [FromRoute] int dataTableId, CancellationToken cancellationToken)
+ {
+ return await _dataSourceService.AddDataTableAsync(dataSourceId, dataTableId, cancellationToken);
+ }
+
+ [HttpDelete("{dataSourceId}/tables/{dataTableId}")]
+ [Authorize(Policy = Policies.DataSourceEditor)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task DeleteDataTableLink([FromRoute] int dataSourceId, [FromRoute] int dataTableId, CancellationToken cancellationToken)
+ {
+ await _dataSourceService.DeleteDataTableAsync(dataSourceId, dataTableId, cancellationToken);
+ return NoContent();
+ }
+
+ [HttpGet("{dataSourceId}/role-assignments")]
+ [Authorize(Policy = Policies.DataSourceViewer)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task>> GetRoleAssignments([FromRoute] int dataSourceId, CancellationToken cancellationToken)
+ {
+ return Ok(await _dataSourceService.GetRoleAssignmentsAsync(new List { dataSourceId }, cancellationToken));
+ }
+
+ [HttpPost("{dataSourceId}/role-assignments/batch")]
+ [Authorize(Policy = Policies.DataSourceEditor)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task>> SaveRoleAssignments([FromRoute] int dataSourceId, [FromBody] Batch roleAssignments, CancellationToken cancellationToken)
+ {
+ return Ok(await _dataSourceService.SaveRoleAssignmentsAsync(dataSourceId, roleAssignments, cancellationToken));
+ }
+
+ [HttpPost("{dataSourceId}/datacolumn/values")]
+ [Authorize(Policy = Policies.DataSourceViewer)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task>> GetColumnValues([FromRoute] int dataSourceId, [FromBody] ColumnQueryConfig columnQueryConfig, CancellationToken cancellationToken)
+ {
+ try
+ {
+ return Ok(await _dataQueryService.GetColumnValues(columnQueryConfig, cancellationToken));
+ }
+ catch (UnauthorizedAccessException)
+ {
+ var dataColumnIds = new List { columnQueryConfig.DataColumnId };
+ if (columnQueryConfig.Filter != null)
+ dataColumnIds.Add(columnQueryConfig.Filter.DataColumnId);
+ _logger.LogWarning(
+ "Attempted to {method} for data source {dataSourceId} with the following data column ids {dataColumnIds}",
+ nameof(GetColumnValues),
+ dataSourceId,
+ dataColumnIds);
+ return Forbid();
+ }
+ }
+
+ [HttpPost("{dataSourceId}/query")]
+ [Authorize(Policy = Policies.DataSourceViewer)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task>> QueryData([FromRoute] int dataSourceId, [FromBody] QueryConfig query, CancellationToken cancellationToken)
+ {
+ try
+ {
+ return Ok(await _dataQueryService.QueryData(query, cancellationToken));
+ }
+ catch (UnauthorizedAccessException)
+ {
+ _logger.LogWarning(
+ "Attempted to {method} for data source {dataSourceId} with the following data column ids {dataColumnIds}",
+ nameof(QueryData),
+ dataSourceId,
+ query.GetAllColumnIds());
+ return Forbid();
+ }
+ }
+
+ [HttpPost("query")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> GetSqlQuery(QueryConfig queryConfig, CancellationToken cancellationToken)
+ {
+ try
+ {
+ return Ok(await _dataQueryService.BuildSqlQuery(queryConfig, cancellationToken));
+ }
+ catch (UnauthorizedAccessException)
+ {
+ _logger.LogWarning(
+ "Attempted to {method} with the following data column ids {dataColumnIds}",
+ nameof(GetSqlQuery),
+ queryConfig.GetAllColumnIds());
+ return Forbid();
+ }
+ }
+
+
+ [HttpPost("{dataSourceId}/download")]
+ [Authorize(Policy = Policies.DataSourceViewer)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task DownloadDataSheet([FromRoute] int dataSourceId, [FromBody] ExportDataSheetRequest request, CancellationToken cancellationToken)
+ {
+ var stream = await _exportService.DownloadDataSheet(request, cancellationToken);
+ return File(stream, "application/octet-stream");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Controllers/DatabasesController.cs b/src/Strata.Analytics.Api/Controllers/DatabasesController.cs
new file mode 100644
index 0000000..ca618bd
--- /dev/null
+++ b/src/Strata.Analytics.Api/Controllers/DatabasesController.cs
@@ -0,0 +1,27 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using Strata.Analytics.Biz.Database;
+
+namespace Strata.Analytics.Api.Controllers
+{
+ [ApiController]
+ [ApiVersion("1.0")]
+ [Route("api/v{api-version:apiVersion}/[controller]")]
+ public class DatabasesController : ControllerBase
+ {
+ private readonly IDatabaseService _databaseService;
+
+ public DatabasesController(IDatabaseService databaseService)
+ {
+ _databaseService = databaseService;
+ }
+
+ [HttpGet("")]
+ public async Task> GetDatabases(CancellationToken cancellationToken)
+ {
+ return await _databaseService.GetDatabasesAsync(cancellationToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Controllers/EncountersController.cs b/src/Strata.Analytics.Api/Controllers/EncountersController.cs
new file mode 100644
index 0000000..0ad5d65
--- /dev/null
+++ b/src/Strata.Analytics.Api/Controllers/EncountersController.cs
@@ -0,0 +1,32 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Threading;
+using System.Threading.Tasks;
+using Strata.Analytics.Biz.Encounters;
+using Strata.Analytics.Api.Models;
+
+namespace Strata.Analytics.Api.Controllers
+{
+ [ApiController]
+ [ApiVersion("1.0")]
+ [Route("api/v{api-version:apiVersion}/[controller]")]
+ public class EncountersController : ControllerBase
+ {
+ private readonly IEncounterService _encounterService;
+
+ public EncountersController(IEncounterService encounterService)
+ {
+ _encounterService = encounterService;
+ }
+
+ ///
+ /// Given an encounter record number, return the encounter id
+ ///
+ [HttpPost("encounter-id")]
+ public async Task GetEncounterId([FromBody] EncounterInfo encounterInfo, CancellationToken cancellationToken)
+ {
+ // Needs to be POST to prevent PHI data in url string
+ return await _encounterService.GetEncounterIdAsync(encounterInfo.EncounterRecordNumber, cancellationToken);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Controllers/ExtractsController.cs b/src/Strata.Analytics.Api/Controllers/ExtractsController.cs
new file mode 100644
index 0000000..05fdb19
--- /dev/null
+++ b/src/Strata.Analytics.Api/Controllers/ExtractsController.cs
@@ -0,0 +1,236 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Strata.Analytics.Biz.Extracts.Models;
+using Strata.Analytics.Biz.Extracts.Services;
+using Microsoft.AspNetCore.Http;
+using Hangfire;
+using Microsoft.AspNetCore.Authorization;
+using Strata.Analytics.Api.Authorization;
+using Strata.Analytics.Models.Utils;
+using System;
+using Strata.Analytics.Biz.Extracts.Jobs;
+using System.Security.Claims;
+using Hangfire.States;
+using Strata.CoreLib.Claims;
+using Strata.CoreLib.Claims.Extensions;
+
+namespace Strata.Analytics.Api.Controllers
+{
+ [Authorize(Policy = Policies.IsAdmin)]
+ [ApiController]
+ [ApiVersion("1.0")]
+ [Route("api/v{api-version:apiVersion}/[controller]")]
+ public class ExtractsController : ControllerBase
+ {
+ private readonly IExtractService _extractService;
+ private readonly IBackgroundJobClient _backgroundJobClient;
+ private readonly IExtractExportHistoryService _historyService;
+ private readonly IExtractScheduleService _extractScheduleService;
+ private readonly ClaimsPrincipal _user;
+
+ public ExtractsController(IExtractService extractService, IBackgroundJobClient backgroundJobClient, IExtractExportHistoryService historyService, IExtractScheduleService extractScheduleService, IClaimsPrincipalAccessor claimsPrincipalAccessor)
+ {
+ _extractService = extractService;
+ _backgroundJobClient = backgroundJobClient;
+ _historyService = historyService;
+ _extractScheduleService = extractScheduleService;
+ _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
+ }
+
+ [HttpGet]
+ public async Task> GetExtractsAsync(CancellationToken cancellationToken)
+ {
+ return await _extractService.GetAllAsync(cancellationToken);
+ }
+
+ [HttpGet("{extractId}")]
+ [Authorize(Policy = Policies.ExtractViewer)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> GetExtractById(int extractId, CancellationToken cancellationToken)
+ {
+
+ var extract = await _extractService.GetByIdAsync(extractId, cancellationToken);
+ if (extract == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(extract);
+ }
+
+ [HttpDelete("{extractId}")]
+ [Authorize(Policy = Policies.CustomSqlAccess)]
+ [Authorize(Policy = Policies.ExtractEditor)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task DeleteExtract(int extractId, CancellationToken cancellationToken)
+ {
+ if (!await _extractService.ContainsAsync(extractId, cancellationToken))
+ {
+ return NotFound();
+ }
+
+ await _extractScheduleService.DeleteByExtractIdAsync(extractId, cancellationToken);
+ await _extractService.DeleteAsync(extractId, cancellationToken);
+ return NoContent();
+ }
+
+ [HttpPost]
+ public async Task> CreateExtract(ExtractCreateInfo extractCreateInfo, CancellationToken cancellationToken)
+ {
+ return await _extractService.CreateAsync(extractCreateInfo, cancellationToken);
+ }
+
+ [HttpPut("{extractId}")]
+ [Authorize(Policy = Policies.CustomSqlAccess)]
+ [Authorize(Policy = Policies.ExtractEditor)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> UpdateExtract([FromRoute] int extractId, [FromBody] ExtractSaveInfo extractSaveInfo, CancellationToken cancellationToken)
+ {
+ if (!await _extractService.ContainsAsync(extractId, cancellationToken))
+ {
+ return NotFound();
+ }
+
+ return Ok(await _extractService.UpdateAsync(extractId, extractSaveInfo, cancellationToken));
+ }
+
+
+ [HttpPost("export/{extractId}")]
+ [ProducesResponseType(StatusCodes.Status202Accepted)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(StatusCodes.Status500InternalServerError)]
+ [ProducesDefaultResponseType]
+ [Authorize(Policy = Policies.ExtractEditor)]
+ public async Task ExportExtract(int extractId, [FromBody] ExtractExportRequest request, CancellationToken cancellationToken)
+ {
+ if (!await _extractService.ContainsAsync(request.ExtractId, cancellationToken))
+ {
+ return NotFound();
+ }
+
+ request.Timezone = _user.GetTimezone();
+
+ var jobId = _backgroundJobClient.Enqueue(job =>
+ job.ExecuteExport(request.OutputFileName, _user.GetDatabaseGuid(), _user.GetStrataId(), _user.GetUserGuid(), request, null, cancellationToken));
+
+ return Accepted(new { jobId });
+ }
+
+ [HttpDelete("{extractId}/history/{extractHistoryId}/cancel/")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(StatusCodes.Status500InternalServerError)]
+ [ProducesDefaultResponseType]
+ [Authorize(Policy = Policies.ExtractEditor)]
+ public async Task> CancelExportExtract([FromRoute] int extractId, [FromRoute] int extractHistoryId, CancellationToken cancellationToken)
+ {
+ var history = await _historyService.CancelExtractExport(extractHistoryId, cancellationToken);
+
+ if (history == null) return NotFound();
+
+ _backgroundJobClient.ChangeState(history.JobId, new DeletedState {Reason = "Job cancelled manually" });
+
+ return Ok(history);
+ }
+
+ [HttpGet("history/{extractId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ [Authorize(Policy = Policies.ExtractViewer)]
+ public async Task>> GetHistoryById(int extractId, CancellationToken cancellationToken)
+ {
+ if (!await _extractService.ContainsAsync(extractId, cancellationToken))
+ {
+ return NotFound();
+ }
+
+ return Ok(await _historyService.GetByExtractIdAsync(extractId, cancellationToken));
+ }
+
+ [HttpGet("{extractId}/role-assignments")]
+ [Authorize(Policy = Policies.ExtractViewer)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task>> GetRoleAssignments([FromRoute] int extractId, CancellationToken cancellationToken)
+ {
+ return Ok(await _extractService.GetRoleAssignmentsAsync(extractId, cancellationToken));
+ }
+
+ [HttpPost("{extractId}/role-assignments/batch")]
+ [Authorize(Policy = Policies.ExtractEditor)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task>> SaveRoleAssignments(
+ [FromRoute] int extractId, [FromBody] Batch roleAssignments, CancellationToken cancellationToken)
+ {
+ return Ok(await _extractService.SaveRoleAssignmentsAsync(extractId, roleAssignments, cancellationToken));
+ }
+
+ [HttpPost("{extractId}/schedule")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ [Authorize(Policy = Policies.ExtractEditor)]
+ public async Task> CreateExtractSchedule([FromRoute] int extractId, [FromBody] ExtractScheduleSaveInfo saveInfo, CancellationToken cancellationToken)
+ {
+ if (!await _extractService.ContainsAsync(extractId, cancellationToken))
+ {
+ return NotFound();
+ }
+
+ return Ok(await _extractScheduleService.CreateAsync(saveInfo, cancellationToken));
+ }
+
+ [HttpPut("{extractId}/schedule/{extractScheduleId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ [Authorize(Policy = Policies.ExtractEditor)]
+ public async Task> UpdateExtractSchedule([FromRoute] int extractId, [FromRoute] int extractScheduleId, [FromBody] ExtractScheduleSaveInfo saveInfo, CancellationToken cancellationToken)
+ {
+ if (!await _extractScheduleService.ContainsAsync(extractScheduleId, cancellationToken))
+ {
+ return NotFound();
+ }
+
+ return Ok(await _extractScheduleService.UpdateAsync(extractScheduleId, saveInfo, cancellationToken));
+ }
+
+ [HttpGet("{extractId}/schedule")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesDefaultResponseType]
+ [Authorize(Policy = Policies.ExtractEditor)]
+ public async Task> GetScheduleByExtractId([FromRoute] int extractId, CancellationToken cancellationToken)
+ {
+ return Ok(await _extractScheduleService.GetByExtractIdAsync(extractId, cancellationToken));
+ }
+
+ [HttpDelete("{extractId}/schedule/{extractScheduleId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ [Authorize(Policy = Policies.ExtractEditor)]
+ public async Task DeleteExtractSchedule([FromRoute] int extractId, [FromRoute] int extractScheduleId, CancellationToken cancellationToken)
+ {
+ if (!await _extractScheduleService.ContainsAsync(extractScheduleId, cancellationToken))
+ {
+ return NotFound();
+ }
+
+ await _extractScheduleService.DeleteAsync(extractScheduleId, cancellationToken);
+
+ return Ok();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Controllers/FeatureContoller.cs b/src/Strata.Analytics.Api/Controllers/FeatureContoller.cs
new file mode 100644
index 0000000..f54e99b
--- /dev/null
+++ b/src/Strata.Analytics.Api/Controllers/FeatureContoller.cs
@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+using Strata.FeatureFlags.Client;
+using System.Security.Claims;
+using Strata.CoreLib.Claims;
+using Strata.CoreLib.Claims.Extensions;
+
+namespace Strata.Analytics.Api.Controllers
+{
+ [ApiController]
+ [ApiVersion("1.0")]
+ [Route("api/v{api-version:apiVersion}/[controller]")]
+ public class FeatureController : ControllerBase
+ {
+ private readonly IFeatureFlagServiceClient _featureFlagService;
+ private readonly ClaimsPrincipal _user;
+
+ public FeatureController(IFeatureFlagServiceClient featureFlagService, IClaimsPrincipalAccessor claimsPrincipalAccessor)
+ {
+ _featureFlagService = featureFlagService;
+ _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal();
+ }
+
+ [HttpGet("dashboards-enabled")]
+ public async Task IsDashboardsEnabled()
+ {
+ return await _featureFlagService.IsEnabledAsync("analytics-dashboards", _user.GetDatabaseGuid());
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Controllers/SavedFiltersController.cs b/src/Strata.Analytics.Api/Controllers/SavedFiltersController.cs
new file mode 100644
index 0000000..7f6b601
--- /dev/null
+++ b/src/Strata.Analytics.Api/Controllers/SavedFiltersController.cs
@@ -0,0 +1,55 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Threading;
+using System.Threading.Tasks;
+using Strata.Analytics.Biz.Workbooks.Services;
+using Microsoft.AspNetCore.Http;
+using Strata.Analytics.Models.Workbooks;
+
+namespace Strata.Analytics.Api.Controllers
+{
+ [ApiController]
+ [ApiVersion("1.0")]
+ [Route("api/v{api-version:apiVersion}/[controller]")]
+ public class SavedFiltersController : ControllerBase
+ {
+ private readonly ISavedFilterService _savedFilterService;
+
+ public SavedFiltersController(ISavedFilterService savedFiltersService)
+ {
+ _savedFilterService = savedFiltersService;
+ }
+
+ [HttpGet("{savedFilterId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> GetSavedFiltersAsync(int savedFilterId, CancellationToken cancellationToken)
+ {
+ var savedFilters = await _savedFilterService.GetSavedFiltersAsync(savedFilterId, cancellationToken);
+ if (savedFilters == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(savedFilters);
+ }
+
+ [HttpDelete("{savedFilterId}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task Delete(int savedFilterId, CancellationToken cancellationToken)
+ {
+ await _savedFilterService.DeleteSavedFiltersAsync(savedFilterId, cancellationToken);
+ return NoContent();
+ }
+
+ [HttpPost]
+ public async Task> Create(SavedFilterInfo savedFilters, CancellationToken cancellationToken)
+ {
+ var cache = await _savedFilterService.CreateSavedFiltersAsync(savedFilters, cancellationToken);
+
+ return cache.SavedFilterId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Controllers/StandardDataSourcesController.cs b/src/Strata.Analytics.Api/Controllers/StandardDataSourcesController.cs
new file mode 100644
index 0000000..e148754
--- /dev/null
+++ b/src/Strata.Analytics.Api/Controllers/StandardDataSourcesController.cs
@@ -0,0 +1,279 @@
+using Hangfire;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Strata.Analytics.Api.Authorization;
+using Strata.Analytics.Api.Models;
+using Strata.Analytics.Biz.Database;
+using Strata.Analytics.Biz.DataQuery;
+using Strata.Analytics.Biz.DataSources.Models;
+using Strata.Analytics.Biz.Schema;
+using Strata.Analytics.Biz.Schema.Jobs;
+using Strata.Analytics.Biz.StandardDataSources.Jobs;
+using Strata.Analytics.Biz.StandardDataSources.Models;
+using Strata.Analytics.Biz.StandardDataSources.Services;
+using Strata.Analytics.Models;
+using Strata.Analytics.Models.Utils;
+using Strata.CoreLib.Claims.Extensions;
+using Strata.DataSchema.Models.Query;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Strata.Analytics.Api.Controllers
+{
+ [ApiController]
+ [ApiVersion("1.0")]
+ [Route("api/v{api-version:apiVersion}/[controller]")]
+ public class StandardDataSourcesController : ControllerBase
+ {
+ private readonly IStandardDataSourceService _standardDataSourceService;
+ private readonly IDataQueryService _dataQueryService;
+ private readonly IDatabaseService _databaseService;
+ private readonly IStandardDataSourcePublishService _standardDataSourcePublishService;
+ private readonly IBackgroundJobClient _backgroundJobClient;
+ private readonly IPublishStandardDataSourcesJob _publishStandardDataSourcesJob;
+ private readonly ILogger _logger;
+
+ public StandardDataSourcesController(
+ IStandardDataSourceService standardDataSourceService,
+ IDataQueryService dataQueryService,
+ IDatabaseService databaseService,
+ IStandardDataSourcePublishService standardDataSourcePublishService,
+ IBackgroundJobClient backgroundJobClient,
+ IPublishStandardDataSourcesJob publishStandardDataSourcesJob,
+ ILogger logger)
+ {
+ _standardDataSourceService = standardDataSourceService;
+ _dataQueryService = dataQueryService;
+ _databaseService = databaseService;
+ _standardDataSourcePublishService = standardDataSourcePublishService;
+ _backgroundJobClient = backgroundJobClient;
+ _publishStandardDataSourcesJob = publishStandardDataSourcesJob;
+ _logger = logger;
+ }
+
+ [HttpGet]
+ public async Task> GetDataSourcesAsync(bool includeTableLinks = false, CancellationToken cancellationToken = default)
+ {
+ return await _standardDataSourceService.GetAllAsync(includeTableLinks, cancellationToken: cancellationToken);
+ }
+
+ [HttpGet("{standardDataSourceId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> GetById(int standardDataSourceId, CancellationToken cancellationToken)
+ {
+ var standardDataSource = await _standardDataSourceService.GetByIdAsync(standardDataSourceId, cancellationToken);
+ if (standardDataSource == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(standardDataSource);
+ }
+
+ [HttpPut("")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [Authorize(Policy = Policies.AuthorizedDbOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> Save(StandardDataSourceSaveInfo standardDataSourceSaveInfo, CancellationToken cancellationToken)
+ {
+ var isFound = await _standardDataSourceService.ContainsAsync(standardDataSourceSaveInfo.StandardDataSourceId, cancellationToken);
+ if (!isFound)
+ {
+ return NotFound();
+ }
+
+ var updatedDataSource = await _standardDataSourceService.SaveAsync(standardDataSourceSaveInfo, cancellationToken);
+
+ return Ok(updatedDataSource);
+ }
+
+ [HttpDelete("{standardDataSourceId}")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [Authorize(Policy = Policies.AuthorizedDbOnly)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task Delete(int standardDataSourceId, CancellationToken cancellationToken)
+ {
+ await _standardDataSourceService.DeleteAsync(standardDataSourceId, cancellationToken);
+ return NoContent();
+ }
+
+ [HttpPost("")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [Authorize(Policy = Policies.AuthorizedDbOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ public async Task> Create(StandardDataSourceSaveInfo standardDataSourceSaveInfo, CancellationToken cancellationToken)
+ {
+ return await _standardDataSourceService.CreateAsync(standardDataSourceSaveInfo, cancellationToken);
+ }
+
+ [HttpGet("{standardDataSourceId}/role-assignments")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [Authorize(Policy = Policies.AuthorizedDbOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task>> GetRoleAssignments([FromRoute] int standardDataSourceId, CancellationToken cancellationToken)
+ {
+ return Ok(await _standardDataSourceService.GetRoleAssignmentsAsync(new List { standardDataSourceId }, cancellationToken));
+ }
+
+ [HttpPost("{standardDataSourceId}/role-assignments/batch")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [Authorize(Policy = Policies.AuthorizedDbOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task>> SaveRoleAssignments([FromRoute] int standardDataSourceId, [FromBody] Batch roleAssignments, CancellationToken cancellationToken)
+ {
+ return Ok(await _standardDataSourceService.SaveRoleAssignmentsAsync(standardDataSourceId, roleAssignments, cancellationToken));
+ }
+
+ [HttpPost("{standardDataSourceId}/datacolumn/values")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [Authorize(Policy = Policies.AuthorizedDbOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task>> GetColumnValues([FromRoute] int standardDataSourceId, [FromBody] ColumnQueryConfig columnQueryConfig, CancellationToken cancellationToken)
+ {
+ try
+ {
+ return Ok(await _dataQueryService.GetColumnValues(columnQueryConfig, cancellationToken));
+ }
+ catch (UnauthorizedAccessException)
+ {
+ var dataColumnIds = new List { columnQueryConfig.DataColumnId };
+ if (columnQueryConfig.Filter != null)
+ dataColumnIds.Add(columnQueryConfig.Filter.DataColumnId);
+ _logger.LogWarning(
+ "Attempted to {method} for standard data source {standardDataSourceId} with the following data column ids {dataColumnIds}",
+ nameof(GetColumnValues),
+ standardDataSourceId,
+ dataColumnIds);
+ return Forbid();
+ }
+ }
+
+ [HttpPost("{standardDataSourceId}/query")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [Authorize(Policy = Policies.AuthorizedDbOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task>> QueryData([FromRoute] int standardDataSourceId, [FromBody] QueryConfig query, CancellationToken cancellationToken)
+ {
+ try
+ {
+ return Ok(await _dataQueryService.QueryData(query, cancellationToken));
+ }
+ catch (UnauthorizedAccessException)
+ {
+ _logger.LogWarning(
+ "Attempted to {method} for standard data source {standardDataSourceId} with the following data column ids {dataColumnsIds}",
+ nameof(QueryData),
+ standardDataSourceId,
+ query.GetAllColumnIds());
+ return Forbid();
+ }
+ }
+
+ [HttpPost("query")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [Authorize(Policy = Policies.AuthorizedDbOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> GetSqlQuery(QueryConfig queryConfig, CancellationToken cancellationToken)
+ {
+ try
+ {
+ return Ok(await _dataQueryService.BuildSqlQuery(queryConfig, cancellationToken));
+ }
+ catch (UnauthorizedAccessException)
+ {
+ _logger.LogWarning(
+ "Attempted to {method} with the following data column ids {dataColumnIds}",
+ nameof(GetSqlQuery),
+ queryConfig.GetAllColumnIds());
+ return Forbid();
+ }
+ }
+
+ [HttpPost("{standardDataSourceId}/include-in-publish")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [Authorize(Policy = Policies.AuthorizedDbOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> IncludeDataSourceInPublish([FromRoute] int standardDataSourceId, CancellationToken cancellationToken)
+ {
+ return Ok(await _standardDataSourceService.SetIsIncludedInStandardModel(standardDataSourceId, true, cancellationToken));
+
+ }
+
+ [HttpPost("{standardDataSourceId}/exclude-from-publish")]
+ [Authorize(Policy = Policies.StrataOnly)]
+ [Authorize(Policy = Policies.AuthorizedDbOnly)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> ExcludeDataSourceInPublish([FromRoute] int standardDataSourceId, CancellationToken cancellationToken)
+ {
+ return Ok(await _standardDataSourceService.SetIsIncludedInStandardModel(standardDataSourceId, false, cancellationToken));
+
+ }
+
+ [HttpPost("publish")]
+ [Authorize(Policy = Policies.AuthorizedDbOnly)]
+ public async Task PublishAsync([FromBody] PublishConfig publishConfig, CancellationToken cancellationToken)
+ {
+ if (publishConfig.StrataId == 0)
+ {
+ var databases = await _databaseService.GetDatabasesAsync(cancellationToken);
+ return _backgroundJobClient.Enqueue(job => job.ExecuteAsync(databases.ToList(), User.GetUsername(), DateTime.UtcNow, CancellationToken.None));
+ }
+
+ if (Debugger.IsAttached && publishConfig.StrataId == User.GetStrataId())
+ {
+ await _publishStandardDataSourcesJob.ExecuteAsync(User.GetDatabaseGuid(), User.GetStrataId(), Guid.NewGuid().ToString(), User.GetUsername(), DateTime.UtcNow, cancellationToken);
+ return "";
+ }
+ var database = await _databaseService.GetDatabaseByStrataId(publishConfig.StrataId, cancellationToken);
+ var parentJobId = _backgroundJobClient.Enqueue(job => job.ExecuteAsync(database.DatabaseGuid, database.StrataId, Guid.NewGuid().ToString(), User.GetUsername(), DateTime.UtcNow, CancellationToken.None));
+
+ return parentJobId;
+ }
+
+ [HttpGet("publish-history")]
+ public async Task>> GetPublicationHistory(CancellationToken cancellationToken)
+ {
+ var publications = await _standardDataSourcePublishService.GetPublicationsAsync(cancellationToken);
+
+ return Ok(publications
+ .GroupBy(x => x.RequestCorrelationId)
+ .Select(x => new PublicationEvent
+ {
+ RequestDate = x.FirstOrDefault().RequestDate,
+ RequestedBy = x.FirstOrDefault().RequestedBy,
+ StandardDataSourceIds = x.Select(y => y.StandardDataSourceId).Distinct(),
+ StrataIds = x.Select(y => y.ClientId).Distinct()
+ }));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Controllers/UserGroupsController.cs b/src/Strata.Analytics.Api/Controllers/UserGroupsController.cs
new file mode 100644
index 0000000..0601aa5
--- /dev/null
+++ b/src/Strata.Analytics.Api/Controllers/UserGroupsController.cs
@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Strata.Users.Client;
+using Strata.Users.Client.UserGroups.Models;
+using System.Linq;
+
+namespace Strata.Analytics.Api.Controllers
+{
+ [ApiController]
+ [ApiVersion("1.0")]
+ [Route("api/v{api-version:apiVersion}/[controller]")]
+ public class UserGroupsController : ControllerBase
+ {
+ private readonly IUsersServiceClient _usersService;
+
+ public UserGroupsController(IUsersServiceClient usersService)
+ {
+ _usersService = usersService;
+ }
+
+ [HttpGet]
+ public async Task> GetUserGroups(CancellationToken cancellationToken)
+ {
+ var userGroups = await _usersService.GetAllUserGroupsAsync(cancellationToken);
+ return userGroups.Where(ug => !ug.IsHidden);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Controllers/UsersController.cs b/src/Strata.Analytics.Api/Controllers/UsersController.cs
new file mode 100644
index 0000000..2968489
--- /dev/null
+++ b/src/Strata.Analytics.Api/Controllers/UsersController.cs
@@ -0,0 +1,29 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Strata.Users.Client;
+using Strata.Users.Client.Users.Models;
+
+namespace Strata.Analytics.Api.Controllers
+{
+ [ApiController]
+ [ApiVersion("1.0")]
+ [Route("api/v{api-version:apiVersion}/[controller]")]
+ public class UsersController : ControllerBase
+ {
+ private readonly IUsersServiceClient _usersService;
+
+ public UsersController(IUsersServiceClient usersService)
+ {
+ _usersService = usersService;
+ }
+
+ [HttpGet]
+ public async Task> GetUsers(CancellationToken cancellationToken)
+ {
+ return await _usersService.GetAllUsersAsync(cancellationToken);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Controllers/WorkbooksController.cs b/src/Strata.Analytics.Api/Controllers/WorkbooksController.cs
new file mode 100644
index 0000000..f577165
--- /dev/null
+++ b/src/Strata.Analytics.Api/Controllers/WorkbooksController.cs
@@ -0,0 +1,109 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Strata.Analytics.Biz.Workbooks.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Authorization;
+using Strata.Analytics.Api.Authorization;
+using Strata.Analytics.Models.Utils;
+using System;
+using Strata.Analytics.Models.Workbooks;
+
+namespace Strata.Analytics.Api.Controllers
+{
+ [ApiController]
+ [ApiVersion("1.0")]
+ [Route("api/v{api-version:apiVersion}/[controller]")]
+ public class WorkbooksController : ControllerBase
+ {
+ private readonly IWorkbookService _workbookService;
+
+ public WorkbooksController(IWorkbookService workbookService)
+ {
+ _workbookService = workbookService;
+ }
+
+ [HttpGet]
+ public async Task> GetWorkbooksAsync(CancellationToken cancellationToken)
+ {
+
+ return await _workbookService.GetAllAsync(cancellationToken);
+ }
+
+ [HttpGet("{workbookId}")]
+ [Authorize(Policy = Policies.WorkbookViewer)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> GetById(int workbookId, CancellationToken cancellationToken)
+ {
+
+ var workbook = await _workbookService.GetByIdAsync(workbookId, cancellationToken);
+ if (workbook == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(workbook);
+ }
+
+ // Delete should return 404 if id doesn't exist. Use Contains instead of loading entire object into memory to check for existence.
+ [HttpDelete("{workbookId}")]
+ [Authorize(Policy = Policies.WorkbookEditor)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task Delete(int workbookId, CancellationToken cancellationToken)
+ {
+ if (!await _workbookService.ContainsAsync(workbookId, cancellationToken))
+ {
+ return NotFound();
+ }
+
+ await _workbookService.DeleteAsync(workbookId, cancellationToken);
+ return NoContent();
+ }
+
+ [HttpPost]
+ public async Task> Create(WorkbookCreateInfo workbookCreateInfo, CancellationToken cancellationToken)
+ {
+ return await _workbookService.CreateAsync(workbookCreateInfo, cancellationToken);
+ }
+
+ [HttpPut("{workbookId}")]
+ [Authorize(Policy = Policies.WorkbookEditor)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesDefaultResponseType]
+ public async Task> UpdateWorkbook([FromRoute] int workbookId, [FromBody] WorkbookSaveInfo workbookSaveInfo, CancellationToken cancellationToken)
+ {
+ if (!await _workbookService.ContainsAsync(workbookId, cancellationToken))
+ {
+ return NotFound();
+ }
+ return await _workbookService.UpdateAsync(workbookId, workbookSaveInfo, cancellationToken);
+ }
+
+
+ [HttpGet("{workbookId}/role-assignments")]
+ [Authorize(Policy = "WorkbookViewer")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task>> GetRoleAssignments([FromRoute] int workbookId, CancellationToken cancellationToken)
+ {
+ return Ok(await _workbookService.GetRoleAssignmentsAsync(workbookId, cancellationToken));
+ }
+
+ [HttpPost("{workbookId}/role-assignments/batch")]
+ [Authorize(Policy = Policies.WorkbookEditor)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesDefaultResponseType]
+ public async Task>> SaveRoleAssignments([FromRoute] int workbookId, [FromBody] Batch roleAssignments, CancellationToken cancellationToken)
+ {
+ return Ok(await _workbookService.SaveRoleAssignmentsAsync(workbookId, roleAssignments, cancellationToken));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Models/EncounterInfo.cs b/src/Strata.Analytics.Api/Models/EncounterInfo.cs
new file mode 100644
index 0000000..13baba9
--- /dev/null
+++ b/src/Strata.Analytics.Api/Models/EncounterInfo.cs
@@ -0,0 +1,7 @@
+namespace Strata.Analytics.Api.Models
+{
+ public class EncounterInfo
+ {
+ public string EncounterRecordNumber { get; set; }
+ }
+}
diff --git a/src/Strata.Analytics.Api/Models/PublicationEvent.cs b/src/Strata.Analytics.Api/Models/PublicationEvent.cs
new file mode 100644
index 0000000..1fe41eb
--- /dev/null
+++ b/src/Strata.Analytics.Api/Models/PublicationEvent.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+
+namespace Strata.Analytics.Models
+{
+ public class PublicationEvent
+ {
+ public DateTime RequestDate { get; set; }
+ public string RequestedBy { get; set; }
+ public IEnumerable StandardDataSourceIds { get; set; }
+ public IEnumerable StrataIds { get; set; }
+ }
+}
diff --git a/src/Strata.Analytics.Api/Models/PublishConfig.cs b/src/Strata.Analytics.Api/Models/PublishConfig.cs
new file mode 100644
index 0000000..c1b4c5f
--- /dev/null
+++ b/src/Strata.Analytics.Api/Models/PublishConfig.cs
@@ -0,0 +1,10 @@
+namespace Strata.Analytics.Api.Models
+{
+ public class PublishConfig
+ {
+ ///
+ /// 0 means to publish to all clients
+ ///
+ public int StrataId { get; set; }
+ }
+}
diff --git a/src/Strata.Analytics.Api/Program.cs b/src/Strata.Analytics.Api/Program.cs
new file mode 100644
index 0000000..fd76441
--- /dev/null
+++ b/src/Strata.Analytics.Api/Program.cs
@@ -0,0 +1,71 @@
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.CommandLineUtils;
+using Microsoft.Extensions.DependencyInjection;
+using Serilog;
+using Strata.Configuration.Client.DependencyInjection;
+using Strata.Logging.DependencyInjection;
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Strata.Analytics.Biz.DbContexts.Factories;
+
+namespace Strata.Analytics.Api
+{
+ [ExcludeFromCodeCoverage]
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ try
+ {
+ var commandLineApplication = new CommandLineApplication(false);
+ var doMigrate = commandLineApplication.Option(
+ "--ef-migrate",
+ "Apply entity framework migrations and exit",
+ CommandOptionType.NoValue);
+
+ commandLineApplication.HelpOption("-? | -h | --help");
+ commandLineApplication.OnExecute(() =>
+ {
+ ExecuteApp(args, doMigrate);
+ return 0;
+ });
+ commandLineApplication.Execute(args);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "There was an unhandled exception that caused the program to crash");
+
+ //This is for serilog to ensure that all of the cached logs are flushed when the app crashes
+ //This is useful so we can capture logs when the application crashes during startup:
+ Log.CloseAndFlush();
+ }
+ }
+
+ public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
+ .UseStrataConfiguration()
+ .UseStrataLogging()
+ .UseStartup();
+
+ public static void ExecuteApp(string[] args, CommandOption doMigrate)
+ {
+ var webHost = CreateWebHostBuilder(args).Build();
+
+ if (doMigrate.HasValue())
+ {
+ Log.Debug("Applying Entity Framework migrations");
+
+ using var scope = webHost.Services.CreateScope();
+ using var context = new AnalyticsDbContextFactory().CreateDbContext(new string[] { });
+ context.Database.Migrate();
+ Log.Debug("All done, closing app");
+ Environment.Exit(0);
+ }
+
+ // no flags provided, so just run the webhost
+ webHost.Run();
+ }
+ }
+}
diff --git a/src/Strata.Analytics.Api/Properties/launchSettings.json b/src/Strata.Analytics.Api/Properties/launchSettings.json
new file mode 100644
index 0000000..693ad3a
--- /dev/null
+++ b/src/Strata.Analytics.Api/Properties/launchSettings.json
@@ -0,0 +1,16 @@
+{
+ "profiles": {
+ "Strata.Analytics.Api": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "Hangfire__RunLocally": "true",
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "AWS_Profile": "sdt-analytics-service-role",
+ "AWS_REGION": "us-east-1",
+ "AWS_S3_US_EAST_1_REGIONAL_ENDPOINT": "regional"
+ },
+ "applicationUrl": "https://localhost:8443"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Strata.Analytics.Api/Startup.cs b/src/Strata.Analytics.Api/Startup.cs
new file mode 100644
index 0000000..b6ebea2
--- /dev/null
+++ b/src/Strata.Analytics.Api/Startup.cs
@@ -0,0 +1,139 @@
+using Hangfire;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ApiExplorer;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Prometheus;
+using Strata.Analytics.Api.Authorization;
+using Strata.Analytics.Biz.DataSources.Models;
+using Strata.Analytics.Biz.Extracts.Models;
+using Strata.Analytics.Biz.Utils;
+using Strata.Analytics.Models.Workbooks;
+using Strata.ApiLib.Core;
+using Strata.ApiLib.Core.Transformers;
+using Strata.CoreLib.Claims.Extensions;
+using Strata.Hangfire.AspNetCore.Filters.Dashboard;
+using Strata.Hangfire.TaskManagement.AspNetCore;
+using Strata.SwaggerExtensions;
+using System.Diagnostics.CodeAnalysis;
+using System.Net.Mime;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using AuthorizationOptions = Strata.Analytics.Api.Authorization.AuthorizationOptions;
+
+namespace Strata.Analytics.Api
+{
+ [ExcludeFromCodeCoverage]
+ public class Startup
+ {
+ private readonly IConfiguration _configuration;
+
+ public Startup(IConfiguration configuration)
+ {
+ _configuration = configuration;
+ }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddStrataPlatformServices(_configuration);
+ services.AddControllers(options =>
+ {
+ //Prevent browsers from caching API responses (this addresses an IE11 issue):
+ options.Filters.Add(
+ new ResponseCacheAttribute()
+ {
+ Location = ResponseCacheLocation.None,
+ NoStore = false,
+ Duration = -1
+ });
+ options.ReturnHttpNotAcceptable = true;
+ options.Filters.Add(new ProducesAttribute(MediaTypeNames.Application.Json));
+ options.Filters.Add(new ConsumesAttribute(MediaTypeNames.Application.Json));
+ options.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()));
+ })
+ .AddJsonOptions(options =>
+ {
+ options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
+ options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+
+ // Uncomment this line if you want enum to be serialized / deserialized as string value instead of int value
+ options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
+
+ // Date and Time Only serialization/deserialization support
+ options.JsonSerializerOptions.Converters.Add(new DateOnlyJsonConverter());
+ options.JsonSerializerOptions.Converters.Add(new TimeOnlyJsonConverter());
+ });
+
+ // authorization
+ services.AddAuthorization(options =>
+ {
+ options.AddPolicy(Policies.DataSourceViewer,
+ policy => policy.Requirements.Add(new DataSourceMinimumRoleRequirement(DataSourceRole.Viewer)));
+ options.AddPolicy(Policies.DataSourceEditor,
+ policy => policy.Requirements.Add(new DataSourceMinimumRoleRequirement(DataSourceRole.Editor)));
+ options.AddPolicy(Policies.WorkbookViewer,
+ policy => policy.Requirements.Add(new WorkbookMinimumRoleRequirement(WorkbookRole.Viewer)));
+ options.AddPolicy(Policies.WorkbookEditor,
+ policy => policy.Requirements.Add(new WorkbookMinimumRoleRequirement(WorkbookRole.Editor)));
+ options.AddPolicy(Policies.ExtractViewer,
+ policy => policy.Requirements.Add(new ExtractMinimumRoleRequirement(ExtractRole.Viewer)));
+ options.AddPolicy(Policies.ExtractEditor,
+ policy => policy.Requirements.Add(new ExtractMinimumRoleRequirement(ExtractRole.Editor)));
+ options.AddPolicy(Policies.CustomSqlAccess,
+ policy => policy.Requirements.Add(new ExtractCustomSqlStrataOnlyRoleRequirement()));
+ options.AddPolicy(Policies.StrataOnly,
+ policy => policy.RequireAssertion(context => context.User.IsSdtEmployee()));
+ options.AddPolicy(Policies.IsAdmin,
+ policy => policy.RequireAssertion(context => context.User.IsAdmin()));
+ options.AddPolicy(Policies.AuthorizedDbOnly, policy => policy.Requirements.Add(new AuthorizedDbMinimumRoleRequirement()));
+ });
+ services
+ .AddOptions()
+ .Configure((options, configuration) =>
+ {
+ configuration.Bind("authorization", options);
+ })
+ .ValidateDataAnnotations();
+
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+
+ // configure hangfire
+ services.AddStrataHangfire();
+ services.AddStrataHangfireTaskManagement();
+ services.AddAnalyticsServices(_configuration);
+
+ services.AddRouting();
+ services.AddSwagger(_configuration);
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
+ {
+ app.UseRouting();
+
+ app.UseStrataPlatformMiddleware();
+ app.UseMetricServer();
+ app.UseHttpMetrics();
+ app.ConfigureSwagger(env, provider);
+ app.UseStrataHangfireTaskManagement();
+
+#if DEBUG
+ app.UseHangfireDashboard("/hangfire", new DashboardOptions
+ {
+ Authorization = new[] { new AllowAnyoneDashboardAuthorizationFilter() }
+ });
+#endif
+ app.UseEndpoints(endpoints => endpoints.MapControllers().RequireAuthorization());
+ }
+ }
+}
diff --git a/src/Strata.Analytics.Api/Strata.Analytics.Api.csproj b/src/Strata.Analytics.Api/Strata.Analytics.Api.csproj
new file mode 100644
index 0000000..159e9be
--- /dev/null
+++ b/src/Strata.Analytics.Api/Strata.Analytics.Api.csproj
@@ -0,0 +1,35 @@
+
+
+
+ net6.0
+ InProcess
+
+
+
+ true
+ $(NoWarn);1591
+ true
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Strata.Analytics.Api/appsettings.Development.json b/src/Strata.Analytics.Api/appsettings.Development.json
new file mode 100644
index 0000000..eae734a
--- /dev/null
+++ b/src/Strata.Analytics.Api/appsettings.Development.json
@@ -0,0 +1,30 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ },
+ "hangfire": {
+ "redis": {
+ "prefix": "analytics-jobs-service",
+ "configOptions": "ddenredisc01.sdt.local:6379,ddenredisc02.sdt.local:6379,ddenredisc03.sdt.local:6379,ddenredisc04.sdt.local:6379,ddenredisc05.sdt.local:6379,ddenredisc06.sdt.local:6379"
+ }
+ },
+ "endpoints": {
+ "dataschema": "https://dataschema-api.dev.stratanetwork.net/",
+ //"dataschema": "https://localhost:9443/",
+ "systemsettings": "https://settings-api.dev.stratanetwork.net/"
+ },
+ "strataApi": {
+ "clientSecret": "V_okTrkWYBs-PqMP.inbJShIvMGWcpdqCDq_ZjntVF.ydgRCoYaDVJYEWEIdsjJ_"
+ },
+ "Snowflake": {
+ "Url": "https://stratadev.us-east-1.privatelink.snowflakecomputing.com",
+ "Account": "stratadev"
+ },
+ "presignedUrlExpirationInMinutes": 5,
+ "ServerSideEncryptionS3Key": "arn:aws:kms:us-east-1:809820578507:key/c8f62293-5726-4342-b614-9b23bf2043c9"
+
+}
diff --git a/src/Strata.Analytics.Api/appsettings.json b/src/Strata.Analytics.Api/appsettings.json
new file mode 100644
index 0000000..cf57e3f
--- /dev/null
+++ b/src/Strata.Analytics.Api/appsettings.json
@@ -0,0 +1,17 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
+ "Version": "0.0.0",
+ "configuration": {
+ "basicAuthUsername": "BkQsRDdmdjkFNhKVuTI.TpujQJXuKhGDcV.F_hjADMRTaEUWMJnviaesoOrhhfnz",
+ "basicAuthPassword": "GWPIIWECDqh.hjDNdpxVEfeFe-bYFwPSx-oBGFD_od_SUQVk-QqTIkZY_NXMeqeo"
+ },
+ "StrataConfigServerBaseUrl": "https://configuration.dev.stratanetwork.net",
+ "authorization": {
+ "authorizedDbGuid": "45e3b554-86a5-4b43-875d-3da7eed909d8"
+ }
+}
diff --git a/src/Strata.Analytics.Biz/AnalyticsServiceCollectionExtensions.cs b/src/Strata.Analytics.Biz/AnalyticsServiceCollectionExtensions.cs
new file mode 100644
index 0000000..83e43f3
--- /dev/null
+++ b/src/Strata.Analytics.Biz/AnalyticsServiceCollectionExtensions.cs
@@ -0,0 +1,185 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Strata.Hangfire.Configuration;
+using Strata.SqlTools.Configuration.Postgres;
+using Strata.Analytics.Biz.DbContexts;
+using System.Reflection;
+using Amazon;
+using Amazon.S3;
+using Hangfire;
+using Strata.Analytics.Biz.DataSources.Services;
+using Strata.Analytics.Biz.Extracts.Export;
+using Strata.Analytics.Biz.Workbooks.Services;
+using Strata.Id.Client;
+using Strata.Analytics.Biz.Extracts.Services;
+using Strata.Analytics.Biz.DbContexts.Snowflake;
+using Strata.CoreLib.Claims;
+using Strata.CoreLib.Claims.Extensions;
+using Strata.SnowflakeLib.Configuration;
+using Strata.Analytics.Biz.Encounters;
+using Strata.Analytics.Biz.DataQuery;
+using Strata.Analytics.Biz.Extracts.Jobs;
+using Strata.Analytics.Biz.Hangfire;
+using Microsoft.EntityFrameworkCore;
+using Strata.Analytics.Biz.SystemSettings;
+using Strata.SqlTools.Configuration.Common.AsyncFactory;
+using Strata.Hangfire.TaskManagement.Configuration;
+using Strata.Analytics.Biz.Extracts;
+using Strata.Analytics.Biz.ContentMigration.Services;
+using Microsoft.AspNetCore.Http;
+using Strata.Analytics.Biz.DbContexts.Jazz;
+using Strata.Analytics.Biz.StandardDataSources.Services;
+using Strata.Replication.Client;
+using Strata.Analytics.Biz.Database;
+using Strata.Analytics.Biz.Schema.Jobs;
+using Strata.Analytics.Biz.Schema;
+using OfficeOpenXml;
+using Strata.Analytics.Biz.Export.Services;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static class AnalyticsServiceCollectionExtensions
+ {
+ public static IServiceCollection AddAnalyticsServices(this IServiceCollection services, IConfiguration configuration)
+ {
+ // EPPlus license
+ ExcelPackage.LicenseContext = LicenseContext.Commercial;
+
+ services.AddUsersServiceClient();
+ // core services
+ services.AddHttpContextAccessor();
+ services.AddClaimsPrincipalAccessor();
+
+ // external services
+ services.AddIdServiceClient();
+ services.AddSystemSettingsServiceClient();
+ services.AddReplicationServiceClient();
+
+ // postgres
+ services.AddPostgres((options, _) =>
+ {
+ options.ConnectionString = AnalyticsDbContext.GetConnectionString(configuration);
+ });
+
+ //jazz db
+ services.AddAsyncDbContextFactory(options =>
+ {
+ options
+ .WithConnectionString((provider, token) => provider.GetConnectionStringFromSmc(token))
+ .WithDbContextOptions((connectionString, serviceProvider, builder) => builder.UseSqlServer(connectionString));
+ });
+
+ services.AddScoped(provider =>
+ {
+ var factory = provider.GetRequiredService>();
+ var httpContext = provider.GetRequiredService();
+ var dbContext = factory.CreateDbContextAsync(httpContext?.HttpContext?.RequestAborted ?? default)
+ .GetAwaiter()
+ .GetResult();
+
+ return dbContext;
+ });
+
+ services.AddScoped();
+ services.AddIdServiceClient();
+ services.AddFeatureFlagServiceClient();
+ services.AddDataSchemaService();
+
+ services.AddDataQueryService(configuration);
+
+
+ services.AddHangfireJobDependencies();
+
+ services.AddExtractServices(configuration);
+
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+
+ services.AddAutoMapper(Assembly.GetExecutingAssembly());
+
+ services.ConfigureHangfireOptionsFromAws(options =>
+ {
+ options.Schema = "analytics";
+ });
+ services.ConfigureTaskManagementOptions(options => options.SourceName = "Analytics");
+
+ return services;
+ }
+
+
+ ///
+ /// Registers dependencies for batch and recurring jobs
+ ///
+ private static void AddHangfireJobDependencies(this IServiceCollection services)
+ {
+ services.AddScoped();
+ services.TryAddScoped();
+ }
+
+ ///
+ /// Registers instance of and all its necessary dependencies
+ ///
+ internal static IServiceCollection AddDataQueryService(this IServiceCollection services, IConfiguration configuration)
+ {
+
+ // snowflake
+ services.AddSnowflake(configuration);
+ services.AddSnowflakeConfigurationOptions(configuration);
+ services.AddSingleton();
+ services.AddScoped(provider =>
+ {
+ var claimsPrincipalAccessor = provider.GetRequiredService();
+ var snowflakeDatabaseContextFactory = provider.GetRequiredService();
+ return snowflakeDatabaseContextFactory.Create(claimsPrincipalAccessor.GetCurrentClaimsPrincipal().GetDatabaseGuid().ToString("N"));
+ });
+
+ services.AddScoped();
+
+ return services;
+ }
+
+ ///
+ /// Registers all dependencies for Extracts services
+ ///
+ internal static IServiceCollection AddExtractServices(this IServiceCollection services, IConfiguration configuration)
+ {
+
+ services.Configure(o =>
+ {
+ o.MaxFileSize = configuration.GetValue("extracts:maxFileSize", 1073741824);
+ o.MaxRowsPerFile = configuration.GetValue("extracts:maxRowsPerFile", 1000000);
+ o.MaxS3Keys = configuration.GetValue("extracts:maxS3Keys", 100);
+ o.SnowflakeMaxFileSize = configuration.GetValue("extracts:snowflakeMaxFileSize", 16777216);
+ o.FileExtension = configuration.GetValue("extracts:fileExtension", "txt");
+ o.OutboundFileExtension = configuration.GetValue("extracts:outboundFileExtension", "csv");
+ }
+ );
+
+ AWSConfigsS3.UseSignatureVersion4 = true;
+ services.AddAWSService();
+ services.AddCachedSmcServiceClient();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+
+ return services;
+ }
+ }
+}
diff --git a/src/Strata.Analytics.Biz/ContentMigration/ContentMigrationAutoMapperProfile.cs b/src/Strata.Analytics.Biz/ContentMigration/ContentMigrationAutoMapperProfile.cs
new file mode 100644
index 0000000..f5a917d
--- /dev/null
+++ b/src/Strata.Analytics.Biz/ContentMigration/ContentMigrationAutoMapperProfile.cs
@@ -0,0 +1,15 @@
+using AutoMapper;
+using Strata.Analytics.Biz.ContentMigration.Models;
+using Strata.Analytics.Models.Workbooks.WorksheetContent;
+
+namespace Strata.Analytics.Biz.ContentMigration
+{
+ public class ContentMigrationAutoMapperProfile : Profile
+ {
+ public ContentMigrationAutoMapperProfile()
+ {
+ CreateMap();
+ CreateMap().ReverseMap();
+ }
+ }
+}
diff --git a/src/Strata.Analytics.Biz/ContentMigration/Helpers/ContentMigrationHelpers.cs b/src/Strata.Analytics.Biz/ContentMigration/Helpers/ContentMigrationHelpers.cs
new file mode 100644
index 0000000..8ff105e
--- /dev/null
+++ b/src/Strata.Analytics.Biz/ContentMigration/Helpers/ContentMigrationHelpers.cs
@@ -0,0 +1,203 @@
+using Strata.Analytics.Biz.ContentMigration.Models;
+using Strata.Analytics.Biz.ContentMigration.Models.Calculations;
+using Strata.DataSchema.Models.Query;
+using Strata.DataSchema.Models.Schema;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using WorksheetContent = Strata.Analytics.Models.Workbooks.WorksheetContent;
+
+namespace Strata.Analytics.Biz.ContentMigration.Helpers
+{
+ public static class ContentMigrationHelpers
+ {
+ #region Filters
+ public static WorksheetContent.Filter CrosswalkFilter(DataColumn dataColumn, int dataSourceId, string name, IEnumerable searchConditions, MigrationResult migrationResult, DatePart? datePart)
+ {
+ var filter = new WorksheetContent.Filter(dataColumn, dataSourceId, name, datePart ?? DatePart.FiscalYear);
+ foreach (var condition in searchConditions)
+ {
+ if (string.IsNullOrWhiteSpace(condition.Value)) continue;
+
+ var members = condition.Value
+ .Split(',')
+ .Select(m => m.Trim())
+ .Where(m => !string.IsNullOrWhiteSpace(m))
+ .Select(v => (object)v);
+
+ if (condition.FilterOp == eFilterOperation.In || condition.FilterOp == eFilterOperation.NotIn)
+ {
+ if (datePart == DatePart.Month) members = TranslateMonths(members);
+ filter.Values = members;
+ filter.ListUseNotIn = condition.FilterOp == eFilterOperation.NotIn;
+ }
+ else
+ {
+ var filterCondition = condition.FilterOp switch
+ {
+ eFilterOperation.Equal => ContentMigrationHelpers.GetFilterCondition(FilterOperator.Equals, LogicalOperator.Or, members),
+ eFilterOperation.NotEqual => ContentMigrationHelpers.GetFilterCondition(FilterOperator.NotEquals, LogicalOperator.Or, members),
+ eFilterOperation.GreaterThan => ContentMigrationHelpers.GetFilterCondition(FilterOperator.GreaterThan, LogicalOperator.Or, members),
+ eFilterOperation.GreaterThanEqualTo => ContentMigrationHelpers.GetFilterCondition(FilterOperator.GreaterThanOrEqualTo, LogicalOperator.Or, members),
+ eFilterOperation.LessThan => ContentMigrationHelpers.GetFilterCondition(FilterOperator.LessThan, LogicalOperator.Or, members),
+ eFilterOperation.LessThanEqualTo => ContentMigrationHelpers.GetFilterCondition(FilterOperator.LessThanOrEqualTo, LogicalOperator.Or, members),
+ _ => null
+ };
+ if (filterCondition == null)
+ {
+ migrationResult.GeneralErrors.Add($"Can not migrate filter condition {condition.FilterOp} for {name}");
+ continue;
+ };
+ filter.FilterType = FilterType.Conditions;
+ filter.Conditions = filter.Conditions.Append(filterCondition);
+ }
+ };
+ return filter;
+ }
+
+ public static WorksheetContent.Filter CrosswalkDateFilter(DateFilterValue parameter, DataColumn dataColumn, int dataSourceId, string name)
+ {
+ double amountBy30 = parameter.RollingAmount / 30;
+ var filter = parameter.DateTypeVal switch
+ {
+ // rolling amount is less than 8 and greater than 0 -> default to previous 7 days
+ var op when op == DateTypeEnum.RollingDays && parameter.RollingAmount < 8 && parameter.RollingAmount > 0 => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
+ {
+ FilterType = FilterType.Timeframe,
+ DatePart = DatePart.Day,
+ DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
+ DateTimeFrameOffset = -7,
+ DateTimeFrameCount = 7,
+ },
+ // rolling amount is greater than or equal to 8 and less than or equal to 30 -> default to previous 30 days
+ var op when op == DateTypeEnum.RollingDays && parameter.RollingAmount >= 8 && parameter.RollingAmount <= 30 => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
+ {
+ FilterType = FilterType.Timeframe,
+ DatePart = DatePart.Day,
+ DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
+ DateTimeFrameOffset = -30,
+ DateTimeFrameCount = 30,
+ },
+ // rolling amount is greater than 30 and less than 60 -> default to previous 30 days
+ var op when op == DateTypeEnum.RollingDays && parameter.RollingAmount > 30 && parameter.RollingAmount < 60 => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
+ {
+ FilterType = FilterType.Timeframe,
+ DatePart = DatePart.Day,
+ DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
+ DateTimeFrameOffset = -30,
+ DateTimeFrameCount = 30,
+ },
+ // rolling amount is greater than 60 -> default to custom months
+ var op when op == DateTypeEnum.RollingDays && parameter.RollingAmount >= 60 => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
+ {
+ FilterType = FilterType.Timeframe,
+ DatePart = DatePart.Month,
+ DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
+ IsCustom = true,
+ DateTimeFrameOffset = (int)(Math.Round(amountBy30) * -1),
+ DateTimeFrameCount = (int)(Math.Round(amountBy30)),
+ DateTimeFrameCountDisplay = (int)(Math.Round(amountBy30))
+ },
+ // create custom date time month filter for rolling months with x offset
+ var op when op == DateTypeEnum.RollingMonth => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
+ {
+ FilterType = FilterType.Timeframe,
+ DatePart = DatePart.Month,
+ DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
+ IsCustom = true,
+ DateTimeFrameOffset = parameter.RollingAmount * -1,
+ DateTimeFrameCount = parameter.RollingAmount,
+ DateTimeFrameCountDisplay = parameter.RollingAmount,
+ },
+ // default to current month whenever current month is included
+ var op when op == DateTypeEnum.CurrentMonthandCurrentFiscalYear || op == DateTypeEnum.CurrentMonthandCurrentFiscalYearYTD
+ || op == DateTypeEnum.CurrentMonthandPriorFiscalYear || op == DateTypeEnum.CurrentMonthandPriorFiscalYearYTD => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
+ {
+ FilterType = FilterType.Timeframe,
+ DatePart = DatePart.Month,
+ DateTimeFrame = WorksheetContent.DateTimeFrame.This,
+ DateTimeFrameOffset = 0,
+ DateTimeFrameCount = 1,
+ },
+ // defualt to previous month whenever previous month is included
+ var op when op == DateTypeEnum.PriorMonthandCurrentFiscalYear || op == DateTypeEnum.PriorMonthandCurrentFiscalYearYTD
+ || op == DateTypeEnum.PriorMonthandPriorFiscalYear || op == DateTypeEnum.PriorMonthandPriorFiscalYearYTD => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
+ {
+ FilterType = FilterType.Timeframe,
+ DatePart = DatePart.Month,
+ DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
+ DateTimeFrameOffset = -1,
+ DateTimeFrameCount = 1,
+ },
+ DateTypeEnum.PriorFiscalYear => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
+ {
+ FilterType = FilterType.Timeframe,
+ DatePart = DatePart.FiscalYear,
+ DateTimeFrame = WorksheetContent.DateTimeFrame.Previous,
+ DateTimeFrameOffset = -1,
+ DateTimeFrameCount = 1,
+ },
+ DateTypeEnum.CurrentFiscalYear => new WorksheetContent.Filter(dataColumn, dataSourceId, name)
+ {
+ FilterType = FilterType.Timeframe,
+ DatePart = DatePart.FiscalYear,
+ DateTimeFrame = WorksheetContent.DateTimeFrame.This,
+ DateTimeFrameOffset = 0,
+ DateTimeFrameCount = 1,
+ },
+ _ => null
+ };
+ filter.RelatedDataColumnIds = new List { dataColumn.DataColumnId };
+ return filter;
+ }
+
+ private static FilterCondition GetFilterCondition(FilterOperator filterOp, LogicalOperator logicalOp, IEnumerable