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