blob: f702763f35a1fbd4a5fb15560f1b6abb6ffdc72c [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2016 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style
3# license that can be found in the LICENSE file or at
4# https://developers.google.com/open-source/licenses/bsd
5
6"""Classes that implement the advanced search feature page.
7
8The advanced search page simply displays an HTML page with a form.
9The form handler converts the widget-based query into a googley query
10string and redirects the user to the issue list servlet.
11"""
12from __future__ import print_function
13from __future__ import division
14from __future__ import absolute_import
15
16import logging
17import re
18
19from features import savedqueries_helpers
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +020020from framework import flaskservlet
Copybara854996b2021-09-07 19:36:02 +000021from framework import framework_helpers
22from framework import permissions
23from framework import servlet
24from framework import urls
25
26# Patterns for search values that can be words, labels,
27# component paths, or email addresses.
28VALUE_RE = re.compile(r'[-a-zA-Z0-9._>@]+')
29
30
31class IssueAdvancedSearch(servlet.Servlet):
32 """IssueAdvancedSearch shows a form to enter an advanced search."""
33
34 _PAGE_TEMPLATE = 'tracker/issue-advsearch-page.ezt'
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +020035 _MAIN_TAB_MODE = flaskservlet.FlaskServlet.MAIN_TAB_ISSUES
Copybara854996b2021-09-07 19:36:02 +000036
37 # This form *only* redirects to a GET request, and permissions are checked
38 # in that handler.
39 CHECK_SECURITY_TOKEN = False
40
41 def GatherPageData(self, mr):
42 """Build up a dictionary of data values to use when rendering the page.
43
44 Args:
45 mr: commonly used info parsed from the request.
46
47 Returns:
48 Dict of values used by EZT for rendering the page.
49 """
50 # TODO(jrobbins): Allow deep-linking into this page.
51 canned_query_views = []
52 if mr.project_id:
53 with mr.profiler.Phase('getting canned queries'):
54 canned_queries = self.services.features.GetCannedQueriesByProjectID(
55 mr.cnxn, mr.project_id)
56 canned_query_views = [
57 savedqueries_helpers.SavedQueryView(sq, idx + 1, None, None)
58 for idx, sq in enumerate(canned_queries)]
59
60 saved_query_views = []
61 if mr.auth.user_id and self.services.features:
62 with mr.profiler.Phase('getting saved queries'):
63 saved_queries = self.services.features.GetSavedQueriesByUserID(
64 mr.cnxn, mr.me_user_id)
65 saved_query_views = [
66 savedqueries_helpers.SavedQueryView(sq, idx + 1, None, None)
67 for idx, sq in enumerate(saved_queries)
68 if (mr.project_id in sq.executes_in_project_ids or
69 not mr.project_id)]
70
71 return {
72 'issue_tab_mode': 'issueAdvSearch',
73 'page_perms': self.MakePagePerms(mr, None, permissions.CREATE_ISSUE),
74 'canned_queries': canned_query_views,
75 'saved_queries': saved_query_views,
76 }
77
78 def ProcessFormData(self, mr, post_data):
79 """Process a posted advanced query form.
80
81 Args:
82 mr: commonly used info parsed from the request.
83 post_data: HTML form data from the request.
84
85 Returns:
86 String URL to redirect the user to after processing.
87 """
88 # Default to searching open issues in this project.
89 can = post_data.get('can', 2)
90
91 terms = []
92 self._AccumulateANDTerm('', 'words', post_data, terms)
93 self._AccumulateANDTerm('-', 'without', post_data, terms)
94 self._AccumulateANDTerm('label:', 'labels', post_data, terms)
95 self._AccumulateORTerm('component:', 'components', post_data, terms)
96 self._AccumulateORTerm('status:', 'statuses', post_data, terms)
97 self._AccumulateORTerm('reporter:', 'reporters', post_data, terms)
98 self._AccumulateORTerm('owner:', 'owners', post_data, terms)
99 self._AccumulateORTerm('cc:', 'cc', post_data, terms)
100 self._AccumulateORTerm('commentby:', 'commentby', post_data, terms)
101
102 if 'starcount' in post_data:
103 starcount = int(post_data['starcount'])
104 if starcount >= 0:
105 terms.append('starcount:%s' % starcount)
106
107 return framework_helpers.FormatAbsoluteURL(
108 mr, urls.ISSUE_LIST, q=' '.join(terms), can=can)
109
110 def _AccumulateANDTerm(self, operator, form_field, post_data, search_query):
111 """Build a query that matches issues with ALL of the given field values."""
112 user_input = post_data.get(form_field)
113 if user_input:
114 values = VALUE_RE.findall(user_input)
115 search_terms = ['%s%s' % (operator, v) for v in values]
116 search_query.extend(search_terms)
117
118 def _AccumulateORTerm(self, operator, form_field, post_data, search_query):
119 """Build a query that matches issues with ANY of the given field values."""
120 user_input = post_data.get(form_field)
121 if user_input:
122 values = VALUE_RE.findall(user_input)
123 search_term = '%s%s' % (operator, ','.join(values))
124 search_query.append(search_term)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200125
126 # def GetIssueAdvSearchPage(self, **kwargs):
127 # return self.handler(**kwargs)
128
129 # def PostIssueAdvSearchPage(self, **kwargs):
130 # return self.handler(**kwargs)