github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/public/static/js/waterfall.js (about)

     1   /*
     2    ReactJS code for the Waterfall page. Grid calls the Variant class for each distro, and the Variant class renders each build variant for every version that exists. In each build variant we iterate through all the tasks and render them as well. The row of headers is just a placeholder at the moment.
     3   */
     4  
     5  // Returns string from datetime object in "5/7/96 1:15 AM" format
     6  // Used to display version headers
     7  function getFormattedTime(input, userTz, fmt) {
     8    return moment(input).tz(userTz).format(fmt);
     9  }
    10  
    11  function generateURLParameters(params) {
    12   var ret = [];
    13   for (var p in params) {
    14    ret.push(encodeURIComponent(p) + "=" + encodeURIComponent(params[p]));
    15   }
    16   return ret.join("&");
    17  }
    18  
    19  // getParameterByName returns the value associated with a given query parameter.
    20  // Based on: http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
    21  function getParameterByName(name, url) {
    22    name = name.replace(/[\[\]]/g, "\\$&");
    23    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)");
    24    var results = regex.exec(url);
    25    if (!results){
    26      return null;
    27    }
    28    if (!results[2]){
    29      return '';
    30    }
    31    return decodeURIComponent(results[2].replace(/\+/g, " "));
    32  }
    33  
    34  function updateURLParams(bvFilter, taskFilter, skip, baseURL) {
    35    var params = {};
    36    if (bvFilter && bvFilter != '')
    37      params["bv_filter"]= bvFilter;
    38    if (taskFilter && taskFilter != '')
    39      params["task_filter"]= taskFilter; 
    40    params["skip"] = skip
    41  
    42    var paramString = generateURLParameters(params);
    43    window.history.replaceState({}, '', baseURL + "?" + paramString);
    44  }
    45  
    46  var JIRA_REGEX = /[A-Z]{1,10}-\d{1,6}/ig;
    47  
    48  class JiraLink extends React.Component {
    49    constructor(props) {
    50      super(props);
    51    }
    52  
    53    render() {
    54      var contents
    55  
    56      if (_.isString(this.props.children)) {
    57        let tokens = this.props.children.split(/\s/);
    58        let jiraHost = this.props.jiraHost;
    59        
    60        contents = _.map(tokens, function(token, i){
    61          let hasSpace = i !== (tokens.length - 1);
    62          let maybeSpace = hasSpace ? ' ': '';
    63          if(token.match(JIRA_REGEX)) {
    64            let jiraLink = jiraHost+"/browse/"+token;
    65            return (
    66               React.createElement("a", {href: jiraLink}, token+maybeSpace)
    67            );
    68          } else {
    69            return token + maybeSpace;
    70          }
    71        });
    72      } else {
    73        return null;
    74      }
    75      return (
    76        React.createElement("div", null, 
    77          contents
    78        )
    79      );
    80    }
    81  }
    82  
    83  
    84  // The Root class renders all components on the waterfall page, including the grid view and the filter and new page buttons
    85  // The one exception is the header, which is written in Angular and managed by menu.html
    86  class Root extends React.Component{
    87    constructor(props){
    88      super(props);
    89      // Initialize newer|older buttons
    90      var versionsOnPage = _.reduce(_.map(window.serverData.versions, function(version){
    91        return version.authors.length; 
    92      }), function(memo,num){
    93        return memo + num;
    94      });
    95  
    96      this.baseURL = "/waterfall/" + this.props.project;
    97      this.currentSkip = window.serverData.current_skip;
    98      this.nextSkip = this.currentSkip + versionsOnPage; 
    99      this.prevSkip = this.currentSkip - window.serverData.previous_page_count;
   100  
   101      if (this.nextSkip >= window.serverData.total_versions) {
   102        this.nextSkip = -1;
   103      }
   104      if (this.currentSkip <= 0) {
   105        this.prevSkip = -1;
   106      }
   107     
   108      var buildVariantFilter = getParameterByName('bv_filter',window.location.href);
   109      var taskFilter = getParameterByName('task_filter',window.location.href);
   110  
   111      buildVariantFilter = buildVariantFilter || '';
   112      taskFilter = taskFilter || '';
   113      
   114      var collapsed = localStorage.getItem("collapsed") == "true";
   115  
   116      this.state = {
   117        collapsed: collapsed,
   118        shortenCommitMessage: true,
   119        buildVariantFilter: buildVariantFilter,
   120        taskFilter:taskFilter 
   121      };
   122  
   123      // Handle state for a collapsed view, as well as shortened header commit messages
   124      this.handleCollapseChange = this.handleCollapseChange.bind(this);
   125      this.handleHeaderLinkClick = this.handleHeaderLinkClick.bind(this);
   126      this.handleBuildVariantFilter = this.handleBuildVariantFilter.bind(this);
   127      this.handleTaskFilter = this.handleTaskFilter.bind(this);
   128    }
   129  
   130    handleCollapseChange(collapsed) {
   131      localStorage.setItem("collapsed", collapsed);
   132      this.setState({collapsed: collapsed});
   133    }
   134    handleBuildVariantFilter(filter) {
   135      updateURLParams(filter, this.state.taskFilter, this.currentSkip, this.baseURL);
   136      this.setState({buildVariantFilter: filter});
   137    }
   138    handleTaskFilter(filter) {
   139      updateURLParams(this.state.buildVariantFilter, filter, this.currentSkip, this.baseURL);
   140      this.setState({taskFilter: filter});
   141    }
   142    handleHeaderLinkClick(shortenMessage) {
   143      this.setState({shortenCommitMessage: !shortenMessage});
   144    }
   145    render() {
   146      if (this.props.data.rows.length == 0){
   147        return (
   148          React.createElement("div", null, 
   149            "There are no builds for this project."
   150          )
   151          )
   152      }
   153      var collapseInfo = {
   154        collapsed : this.state.collapsed,
   155        activeTaskStatuses : ['failed','system-failed'],
   156      };
   157      return (
   158        React.createElement("div", null, 
   159          React.createElement(Toolbar, {
   160            collapsed: this.state.collapsed, 
   161            onCheck: this.handleCollapseChange, 
   162            baseURL: this.baseURL, 
   163            nextSkip: this.nextSkip, 
   164            prevSkip: this.prevSkip, 
   165            buildVariantFilter: this.state.buildVariantFilter, 
   166            taskFilter: this.state.taskFilter, 
   167            buildVariantFilterFunc: this.handleBuildVariantFilter, 
   168            taskFilterFunc: this.handleTaskFilter}
   169          ), 
   170          React.createElement(Headers, {
   171            shortenCommitMessage: this.state.shortenCommitMessage, 
   172            versions: this.props.data.versions, 
   173            onLinkClick: this.handleHeaderLinkClick, 
   174            userTz: this.props.userTz, 
   175            jiraHost: this.props.jiraHost}
   176          ), 
   177          React.createElement(Grid, {
   178            data: this.props.data, 
   179            collapseInfo: collapseInfo, 
   180            project: this.props.project, 
   181            buildVariantFilter: this.state.buildVariantFilter, 
   182            taskFilter: this.state.taskFilter}
   183          )
   184        )
   185      )
   186    }
   187  }
   188  
   189  
   190  // Toolbar
   191  function Toolbar ({collapsed, 
   192    onCheck, 
   193    baseURL,
   194    nextSkip, 
   195    prevSkip, 
   196    buildVariantFilter, 
   197    taskFilter,
   198    buildVariantFilterFunc, 
   199    taskFilterFunc}) {
   200  
   201    var Form = ReactBootstrap.Form;
   202    return (
   203      React.createElement("div", {className: "row"}, 
   204        React.createElement("div", {className: "col-xs-12"}, 
   205          React.createElement(Form, {inline: true, className: "waterfall-toolbar pull-right"}, 
   206            React.createElement(CollapseButton, {collapsed: collapsed, onCheck: onCheck}), 
   207            React.createElement(FilterBox, {
   208              filterFunction: buildVariantFilterFunc, 
   209              placeholder: "Filter variant", 
   210              currentFilter: buildVariantFilter, 
   211              disabled: false}
   212            ), 
   213            React.createElement(FilterBox, {
   214              filterFunction: taskFilterFunc, 
   215              placeholder: "Filter task", 
   216              currentFilter: taskFilter, 
   217              disabled: collapsed}
   218            ), 
   219            React.createElement(PageButtons, {
   220              nextSkip: nextSkip, 
   221              prevSkip: prevSkip, 
   222              baseURL: baseURL, 
   223              buildVariantFilter: buildVariantFilter, 
   224              taskFilter: taskFilter}
   225            )
   226          )
   227        )
   228      )
   229    )
   230  };
   231  
   232  function PageButtons ({prevSkip, nextSkip, baseURL, buildVariantFilter, taskFilter}) {
   233    var ButtonGroup = ReactBootstrap.ButtonGroup;
   234  
   235    var nextURL= "";
   236    var prevURL= "";
   237  
   238    prevURLParams = {};
   239    nextURLParams = {};
   240  
   241    nextURLParams["skip"] = nextSkip;
   242    prevURLParams["skip"] = prevSkip;
   243    if (buildVariantFilter && buildVariantFilter != '') {
   244      nextURLParams["bv_filter"] = buildVariantFilter;
   245      prevURLParams["bv_filter"] = buildVariantFilter;
   246    }
   247    if (taskFilter && taskFilter != '') {
   248      nextURLParams["task_filter"] = taskFilter;
   249      prevURLParams["task_filter"] = taskFilter;
   250    }
   251    nextURL = "?" + generateURLParameters(nextURLParams);
   252    prevURL = "?" + generateURLParameters(prevURLParams);
   253    return (
   254      React.createElement("span", {className: "waterfall-form-item"}, 
   255        React.createElement(ButtonGroup, null, 
   256          React.createElement(PageButton, {pageURL: prevURL, disabled: prevSkip < 0, directionIcon: "fa-chevron-left"}), 
   257          React.createElement(PageButton, {pageURL: nextURL, disabled: nextSkip < 0, directionIcon: "fa-chevron-right"})
   258        )
   259      )
   260    );
   261  }
   262  
   263  function PageButton ({pageURL, directionIcon, disabled}) {
   264    var Button = ReactBootstrap.Button;
   265    var classes = "fa " + directionIcon;
   266    return (
   267      React.createElement(Button, {href: pageURL, disabled: disabled}, React.createElement("i", {className: classes}))
   268    );
   269  }
   270  
   271  class FilterBox extends React.Component {
   272    constructor(props){
   273      super(props);
   274      this.applyFilter = this.applyFilter.bind(this);
   275    }
   276    applyFilter() {
   277      this.props.filterFunction(this.refs.searchInput.value)
   278    }
   279    render() {
   280      return React.createElement("input", {type: "text", ref: "searchInput", 
   281                    className: "form-control waterfall-form-item", 
   282                    placeholder: this.props.placeholder, 
   283                    value: this.props.currentFilter, onChange: this.applyFilter, 
   284                    disabled: this.props.disabled})
   285    }
   286  }
   287  
   288  class CollapseButton extends React.Component{
   289    constructor(props){
   290      super(props);
   291      this.handleChange = this.handleChange.bind(this);
   292    }
   293    handleChange(event){
   294      this.props.onCheck(this.refs.collapsedBuilds.checked);
   295    }
   296    render() {
   297      return (
   298        React.createElement("span", {className: "semi-muted waterfall-form-item"}, 
   299          React.createElement("span", {id: "collapsed-prompt"}, "Show collapsed view"), 
   300          React.createElement("input", {
   301            className: "checkbox waterfall-checkbox", 
   302            type: "checkbox", 
   303            checked: this.props.collapsed, 
   304            ref: "collapsedBuilds", 
   305            onChange: this.handleChange}
   306          )
   307        )
   308      )
   309    }
   310  }
   311  
   312  // Headers
   313  
   314  function Headers ({shortenCommitMessage, versions, onLinkClick, userTz, jiraHost}) {
   315    return (
   316      React.createElement("div", {className: "row version-header"}, 
   317        React.createElement("div", {className: "variant-col col-xs-2 version-header-rolled"}), 
   318        React.createElement("div", {className: "col-xs-10"}, 
   319          React.createElement("div", {className: "row"}, 
   320          
   321            versions.map(function(version){
   322              if (version.rolled_up) {
   323                return( 
   324                  React.createElement(RolledUpVersionHeader, {
   325                    key: version.ids[0], 
   326                    version: version, 
   327                    userTz: userTz, 
   328                    jiraHost: jiraHost}
   329                  )
   330                );
   331              }
   332              // Unrolled up version, no popover
   333              return (
   334                React.createElement(ActiveVersionHeader, {
   335                  key: version.ids[0], 
   336                  version: version, 
   337                  userTz: userTz, 
   338                  shortenCommitMessage: shortenCommitMessage, 
   339                  onLinkClick: onLinkClick, 
   340                  jiraHost: jiraHost}
   341                )
   342              );
   343            })
   344          
   345          )
   346        )
   347      )
   348    )
   349  }
   350  
   351  
   352  function ActiveVersionHeader({shortenCommitMessage, version, onLinkClick, userTz, jiraHost}) {
   353    var message = version.messages[0];
   354    var author = version.authors[0];
   355    var id_link = "/version/" + version.ids[0];
   356    var commit = version.revisions[0].substring(0,5);
   357    var message = version.messages[0]; 
   358    var formatted_time = getFormattedTime(version.create_times[0], userTz, 'M/D/YY h:mm A' );
   359    const maxChars = 44 
   360    var button;
   361    if (message.length > maxChars) {
   362      // If we shorten the commit message, only display the first maxChars chars
   363      if (shortenCommitMessage) {
   364        message = message.substring(0, maxChars-3) + "...";
   365      }
   366      button = (
   367        React.createElement(HideHeaderButton, {onLinkClick: onLinkClick, shortenCommitMessage: shortenCommitMessage})
   368      );
   369    }
   370  
   371    return (
   372        React.createElement("div", {className: "header-col"}, 
   373          React.createElement("div", {className: "version-header-expanded"}, 
   374            React.createElement("div", {className: "col-xs-12"}, 
   375              React.createElement("div", {className: "row"}, 
   376                React.createElement("a", {className: "githash", href: id_link}, commit), 
   377                formatted_time
   378              )
   379            ), 
   380            React.createElement("div", {className: "col-xs-12"}, 
   381              React.createElement("div", {className: "row"}, 
   382                React.createElement("strong", null, author), " - ", React.createElement(JiraLink, {jiraHost: jiraHost}, message), 
   383                button
   384              )
   385            )
   386          )
   387        )
   388    )
   389  };
   390  
   391  class HideHeaderButton extends React.Component{
   392    constructor(props){
   393      super(props);
   394      this.onLinkClick = this.onLinkClick.bind(this);
   395    }
   396    onLinkClick(event){
   397      this.props.onLinkClick(this.props.shortenCommitMessage);
   398    }
   399    render() {
   400      var textToShow = this.props.shortenCommitMessage ? "more" : "less";
   401      return (
   402        React.createElement("span", {onClick: this.onLinkClick}, " ", React.createElement("a", {href: "#"}, textToShow), " ")
   403      )
   404    }
   405  }
   406  
   407  function RolledUpVersionHeader({version, userTz, jiraHost}){
   408    var Popover = ReactBootstrap.Popover;
   409    var OverlayTrigger = ReactBootstrap.OverlayTrigger;
   410    var Button = ReactBootstrap.Button;
   411    
   412    var versionStr = (version.messages.length > 1) ? "versions" : "version";
   413    var rolledHeader = version.messages.length + " inactive " + versionStr; 
   414   
   415    var popovers = (
   416      React.createElement(Popover, {id: "popover-positioned-bottom", title: ""}, 
   417        
   418          version.ids.map(function(id,i) {
   419            return React.createElement(RolledUpVersionSummary, {
   420              author: version.authors[i], 
   421              commit: version.revisions[i], 
   422              message: version.messages[i], 
   423              versionId: version.ids[i], 
   424              key: id, userTz: userTz, 
   425              createTime: version.create_times[i], 
   426              jiraHost: jiraHost})
   427          })
   428        
   429      )
   430    );
   431  
   432    return (
   433      React.createElement("div", {className: "header-col version-header-rolled"}, 
   434        React.createElement(OverlayTrigger, {trigger: "click", placement: "bottom", overlay: popovers, className: "col-xs-2"}, 
   435            React.createElement("span", {className: "pointer"}, " ", rolledHeader, " ")
   436        )
   437      )
   438    ) 
   439  }; 
   440  function RolledUpVersionSummary ({author, commit, message, versionId, createTime, userTz, jiraHost}) {
   441    var formatted_time = getFormattedTime(new Date(createTime), userTz, 'M/D/YY h:mm A' );
   442    commit =  commit.substring(0,10);
   443      
   444    return (
   445      React.createElement("div", {className: "rolled-up-version-summary"}, 
   446        React.createElement("span", {className: "version-header-time"}, formatted_time), 
   447        React.createElement("br", null), 
   448        React.createElement("a", {href: "/version/" + versionId}, commit), " - ", React.createElement("strong", null, author), 
   449        React.createElement("br", null), 
   450        React.createElement(JiraLink, {jiraHost: jiraHost}, message), 
   451        React.createElement("br", null)
   452      )
   453    );
   454  }
   455  
   456