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