1# Copyright 2015 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Provides a web interface for seeing recently added points."""
6
7from dashboard import list_tests
8from dashboard import request_handler
9from dashboard import utils
10from dashboard.models import graph_data
11
12# Number of points to list if no number of points is specified.
13_DEFAULT_NUM_POINTS = 100
14
15# Max number of tests to use that match a user-specified pattern.
16_MAX_MATCHING_TESTS = 5
17
18
19class NewPointsHandler(request_handler.RequestHandler):
20  """Shows a page with a list of recently added points."""
21
22  def get(self):
23    """Gets the page for viewing recently added points.
24
25    Request parameters:
26      pattern: A test path pattern with asterisk wildcards (optional).
27
28    Outputs:
29      A page showing recently added points.
30    """
31    # Construct a query for recently added Row entities.
32    query = graph_data.Row.query()
33    query = query.order(-graph_data.Row.timestamp)
34
35    # If a maximum number of tests was specified, use it; fall back on default.
36    try:
37      max_tests = int(self.request.get('max_tests', _MAX_MATCHING_TESTS))
38    except ValueError:
39      max_tests = _MAX_MATCHING_TESTS
40
41    # If a test path pattern was specified, filter the query to include only
42    # Row entities that belong to a test that matches the pattern.
43    test_pattern = self.request.get('pattern')
44    num_originally_matching_tests = 0
45    if test_pattern:
46      test_paths = list_tests.GetTestsMatchingPattern(
47          test_pattern, only_with_rows=True)
48      if not test_paths:
49        self.RenderHtml('new_points.html', {
50            'pattern': test_pattern,
51            'error': 'No tests matching pattern: %s' % test_pattern,
52        })
53        return
54
55      # If test_keys contains too many tests, then this query will exceed a
56      # memory limit or time out. So, limit the number of tests and let the
57      # user know that this has happened.
58      num_originally_matching_tests = len(test_paths)
59      if num_originally_matching_tests > max_tests:
60        test_paths = test_paths[:max_tests]
61      test_keys = map(utils.OldStyleTestKey, test_paths)
62      query = query.filter(graph_data.Row.parent_test.IN(test_keys))
63
64    # If a valid number of points was given, use it. Otherwise use the default.
65    try:
66      num_points = int(self.request.get('num_points', _DEFAULT_NUM_POINTS))
67    except ValueError:
68      num_points = _DEFAULT_NUM_POINTS
69
70    # Fetch the Row entities.
71    rows = query.fetch(limit=num_points)
72
73    # Make a list of dicts which will be passed to the template.
74    row_dicts = []
75    for row in rows:
76      row_dicts.append({
77          'test': utils.TestPath(row.parent_test),
78          'added_time': row.timestamp.strftime('%Y-%m-%d %H:%M:%S %Z'),
79          'revision': row.revision,
80          'value': row.value,
81          'error': row.error,
82      })
83
84    error_message = ''
85    if num_originally_matching_tests > max_tests:
86      error_message = ('Pattern originally matched %s tests; only showing '
87                       'points from the first %s tests.' %
88                       (num_originally_matching_tests, max_tests))
89
90    # Render the template with the row information that was fetched.
91    self.RenderHtml('new_points.html', {
92        'pattern': test_pattern,
93        'num_points': num_points,
94        'max_tests': max_tests,
95        'rows': row_dicts,
96        'error': error_message,
97    })
98