blob: 9cb713cc686cae897b0cf3ace98963e180ed5884 [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"""A servlet for project owners to create a new component def."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import logging
12import time
13
14from framework import framework_helpers
15from framework import framework_views
16from framework import jsonfeed
17from framework import permissions
18from framework import servlet
19from framework import urls
20from tracker import component_helpers
21from tracker import tracker_bizobj
22from tracker import tracker_constants
23from tracker import tracker_views
24
25import ezt
26
27
28class ComponentCreate(servlet.Servlet):
29 """Servlet allowing project owners to create a component."""
30
31 _MAIN_TAB_MODE = servlet.Servlet.MAIN_TAB_PROCESS
32 _PAGE_TEMPLATE = 'tracker/component-create-page.ezt'
33
34 def AssertBasePermission(self, mr):
35 """Check whether the user has any permission to visit this page.
36
37 Args:
38 mr: commonly used info parsed from the request.
39 """
40 super(ComponentCreate, self).AssertBasePermission(mr)
41 if not self.CheckPerm(mr, permissions.EDIT_PROJECT):
42 raise permissions.PermissionException(
43 'User is not allowed to administer this project')
44
45 def GatherPageData(self, mr):
46 """Build up a dictionary of data values to use when rendering the page.
47
48 Args:
49 mr: commonly used info parsed from the request.
50
51 Returns:
52 Dict of values used by EZT for rendering the page.
53 """
54 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
55 users_by_id = framework_views.MakeAllUserViews(
56 mr.cnxn, self.services.user,
57 *[list(cd.admin_ids) + list(cd.cc_ids)
58 for cd in config.component_defs])
59 component_def_views = [
60 tracker_views.ComponentDefView(mr.cnxn, self.services, cd, users_by_id)
61 # TODO(jrobbins): future component-level view restrictions.
62 for cd in config.component_defs]
63 for cdv in component_def_views:
64 setattr(cdv, 'selected', None)
65 path = (cdv.parent_path + '>' + cdv.leaf_name).lstrip('>')
66 if path == mr.component_path:
67 setattr(cdv, 'selected', True)
68
69 return {
70 'parent_path': mr.component_path,
71 'admin_tab_mode': servlet.Servlet.PROCESS_TAB_COMPONENTS,
72 'component_defs': component_def_views,
73 'initial_leaf_name': '',
74 'initial_docstring': '',
75 'initial_deprecated': ezt.boolean(False),
76 'initial_admins': [],
77 'initial_cc': [],
78 'initial_labels': [],
79 }
80
81 def ProcessFormData(self, mr, post_data):
82 """Validate and store the contents of the issues tracker admin page.
83
84 Args:
85 mr: commonly used info parsed from the request.
86 post_data: HTML form data from the request.
87
88 Returns:
89 String URL to redirect the user to, or None if response was already sent.
90 """
91 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
92 parent_path = post_data.get('parent_path', '')
93 parsed = component_helpers.ParseComponentRequest(
94 mr, post_data, self.services)
95
96 if parent_path:
97 parent_def = tracker_bizobj.FindComponentDef(parent_path, config)
98 if not parent_def:
99 self.abort(500, 'parent component not found')
100 allow_parent_edit = permissions.CanEditComponentDef(
101 mr.auth.effective_ids, mr.perms, mr.project, parent_def, config)
102 if not allow_parent_edit:
103 raise permissions.PermissionException(
104 'User is not allowed to add a subcomponent here')
105
106 path = '%s>%s' % (parent_path, parsed.leaf_name)
107 else:
108 path = parsed.leaf_name
109
110 leaf_name_error_msg = LeafNameErrorMessage(
111 parent_path, parsed.leaf_name, config)
112 if leaf_name_error_msg:
113 mr.errors.leaf_name = leaf_name_error_msg
114
115 if mr.errors.AnyErrors():
116 self.PleaseCorrect(
117 mr, parent_path=parent_path,
118 initial_leaf_name=parsed.leaf_name,
119 initial_docstring=parsed.docstring,
120 initial_deprecated=ezt.boolean(parsed.deprecated),
121 initial_admins=parsed.admin_usernames,
122 initial_cc=parsed.cc_usernames,
123 initial_labels=parsed.label_strs,
124 )
125 return
126
127 created = int(time.time())
128 creator_id = self.services.user.LookupUserID(
129 mr.cnxn, mr.auth.email, autocreate=False)
130
131 self.services.config.CreateComponentDef(
132 mr.cnxn, mr.project_id, path, parsed.docstring, parsed.deprecated,
133 parsed.admin_ids, parsed.cc_ids, created, creator_id,
134 label_ids=parsed.label_ids)
135
136 return framework_helpers.FormatAbsoluteURL(
137 mr, urls.ADMIN_COMPONENTS, saved=1, ts=int(time.time()))
138
139
140def LeafNameErrorMessage(parent_path, leaf_name, config):
141 """Return an error message for the given component name, or None."""
142 if not tracker_constants.COMPONENT_NAME_RE.match(leaf_name):
143 return 'Invalid component name'
144
145 if parent_path:
146 path = '%s>%s' % (parent_path, leaf_name)
147 else:
148 path = leaf_name
149
150 if tracker_bizobj.FindComponentDef(path, config):
151 return 'That name is already in use.'
152
153 return None