blob: afdddbf6729bb50159c654dbc9cc02af6920bb1a [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""A servlet for project owners to create a new component def."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
Copybara854996b2021-09-07 19:36:02 +000010import time
11
12from framework import framework_helpers
13from framework import framework_views
Copybara854996b2021-09-07 19:36:02 +000014from framework import permissions
15from framework import servlet
16from framework import urls
17from tracker import component_helpers
18from tracker import tracker_bizobj
19from tracker import tracker_constants
20from tracker import tracker_views
21
22import ezt
23
24
25class ComponentCreate(servlet.Servlet):
26 """Servlet allowing project owners to create a component."""
27
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010028 _MAIN_TAB_MODE = servlet.Servlet.MAIN_TAB_PROCESS
Copybara854996b2021-09-07 19:36:02 +000029 _PAGE_TEMPLATE = 'tracker/component-create-page.ezt'
30
31 def AssertBasePermission(self, mr):
32 """Check whether the user has any permission to visit this page.
33
34 Args:
35 mr: commonly used info parsed from the request.
36 """
37 super(ComponentCreate, self).AssertBasePermission(mr)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010038 if not permissions.CanEditProjectConfig(mr, self.services):
Copybara854996b2021-09-07 19:36:02 +000039 raise permissions.PermissionException(
40 'User is not allowed to administer this project')
41
42 def GatherPageData(self, mr):
43 """Build up a dictionary of data values to use when rendering the page.
44
45 Args:
46 mr: commonly used info parsed from the request.
47
48 Returns:
49 Dict of values used by EZT for rendering the page.
50 """
51 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
52 users_by_id = framework_views.MakeAllUserViews(
53 mr.cnxn, self.services.user,
54 *[list(cd.admin_ids) + list(cd.cc_ids)
55 for cd in config.component_defs])
56 component_def_views = [
57 tracker_views.ComponentDefView(mr.cnxn, self.services, cd, users_by_id)
58 # TODO(jrobbins): future component-level view restrictions.
59 for cd in config.component_defs]
60 for cdv in component_def_views:
61 setattr(cdv, 'selected', None)
62 path = (cdv.parent_path + '>' + cdv.leaf_name).lstrip('>')
63 if path == mr.component_path:
64 setattr(cdv, 'selected', True)
65
66 return {
67 'parent_path': mr.component_path,
68 'admin_tab_mode': servlet.Servlet.PROCESS_TAB_COMPONENTS,
69 'component_defs': component_def_views,
70 'initial_leaf_name': '',
71 'initial_docstring': '',
72 'initial_deprecated': ezt.boolean(False),
73 'initial_admins': [],
74 'initial_cc': [],
75 'initial_labels': [],
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010076 }
Copybara854996b2021-09-07 19:36:02 +000077
78 def ProcessFormData(self, mr, post_data):
79 """Validate and store the contents of the issues tracker admin page.
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, or None if response was already sent.
87 """
88 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
89 parent_path = post_data.get('parent_path', '')
90 parsed = component_helpers.ParseComponentRequest(
91 mr, post_data, self.services)
92
93 if parent_path:
94 parent_def = tracker_bizobj.FindComponentDef(parent_path, config)
95 if not parent_def:
96 self.abort(500, 'parent component not found')
97 allow_parent_edit = permissions.CanEditComponentDef(
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010098 mr, self.services, parent_def, config)
Copybara854996b2021-09-07 19:36:02 +000099 if not allow_parent_edit:
100 raise permissions.PermissionException(
101 'User is not allowed to add a subcomponent here')
102
103 path = '%s>%s' % (parent_path, parsed.leaf_name)
104 else:
105 path = parsed.leaf_name
106
107 leaf_name_error_msg = LeafNameErrorMessage(
108 parent_path, parsed.leaf_name, config)
109 if leaf_name_error_msg:
110 mr.errors.leaf_name = leaf_name_error_msg
111
112 if mr.errors.AnyErrors():
113 self.PleaseCorrect(
114 mr, parent_path=parent_path,
115 initial_leaf_name=parsed.leaf_name,
116 initial_docstring=parsed.docstring,
117 initial_deprecated=ezt.boolean(parsed.deprecated),
118 initial_admins=parsed.admin_usernames,
119 initial_cc=parsed.cc_usernames,
120 initial_labels=parsed.label_strs,
121 )
122 return
123
124 created = int(time.time())
125 creator_id = self.services.user.LookupUserID(
126 mr.cnxn, mr.auth.email, autocreate=False)
127
128 self.services.config.CreateComponentDef(
129 mr.cnxn, mr.project_id, path, parsed.docstring, parsed.deprecated,
130 parsed.admin_ids, parsed.cc_ids, created, creator_id,
131 label_ids=parsed.label_ids)
132
133 return framework_helpers.FormatAbsoluteURL(
134 mr, urls.ADMIN_COMPONENTS, saved=1, ts=int(time.time()))
135
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100136 def GetComponentCreatePage(self, **kwargs):
137 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200138
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100139 def PostComponentCreatePage(self, **kwargs):
140 return self.handler(**kwargs)
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200141
Copybara854996b2021-09-07 19:36:02 +0000142
143def LeafNameErrorMessage(parent_path, leaf_name, config):
144 """Return an error message for the given component name, or None."""
145 if not tracker_constants.COMPONENT_NAME_RE.match(leaf_name):
146 return 'Invalid component name'
147
148 if parent_path:
149 path = '%s>%s' % (parent_path, leaf_name)
150 else:
151 path = leaf_name
152
153 if tracker_bizobj.FindComponentDef(path, config):
154 return 'That name is already in use.'
155
156 return None