blob: cd48a80a944a99071308a579c627672dbf387d28 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2018 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 edit/delete a template"""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import collections
12import logging
13import time
14
15import ezt
16
17from framework import authdata
18from framework import framework_bizobj
19from framework import framework_helpers
20from framework import framework_views
21from framework import servlet
22from framework import urls
23from framework import permissions
24from tracker import field_helpers
25from tracker import template_helpers
26from tracker import tracker_bizobj
27from tracker import tracker_helpers
28from tracker import tracker_views
29from proto import tracker_pb2
30from services import user_svc
31
32
33class TemplateDetail(servlet.Servlet):
34 """Servlet allowing project owners to edit/delete an issue template"""
35
36 _MAIN_TAB_MODE = servlet.Servlet.MAIN_TAB_PROCESS
37 _PAGE_TEMPLATE = 'tracker/template-detail-page.ezt'
38 _PROCESS_SUBTAB = servlet.Servlet.PROCESS_TAB_TEMPLATES
39
40 def AssertBasePermission(self, mr):
41 """Check whether the user has any permission to visit this page.
42
43 Args:
44 mr: commonly used info parsed from the request.
45 """
46 super(TemplateDetail, self).AssertBasePermission(mr)
47 template = self.services.template.GetTemplateByName(mr.cnxn,
48 mr.template_name, mr.project_id)
49
50 if template:
51 allow_view = permissions.CanViewTemplate(
52 mr.auth.effective_ids, mr.perms, mr.project, template)
53 if not allow_view:
54 raise permissions.PermissionException(
55 'User is not allowed to view this issue template')
56 else:
57 self.abort(404, 'issue template not found %s' % mr.template_name)
58
59 def GatherPageData(self, mr):
60 """Build up a dictionary of data values to use when rendering the page.
61
62 Args:
63 mr: commonly used info parsed from the request.
64
65 Returns:
66 Dict of values used by EZT for rendering the page.
67 """
68
69 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
70 template = self.services.template.GetTemplateByName(mr.cnxn,
71 mr.template_name, mr.project_id)
72 template_view = tracker_views.IssueTemplateView(
73 mr, template, self.services.user, config)
74 with mr.profiler.Phase('making user views'):
75 users_involved = tracker_bizobj.UsersInvolvedInTemplate(template)
76 users_by_id = framework_views.MakeAllUserViews(
77 mr.cnxn, self.services.user, users_involved)
78 framework_views.RevealAllEmailsToMembers(
79 mr.cnxn, self.services, mr.auth, users_by_id, mr.project)
80 field_name_set = {fd.field_name.lower() for fd in config.field_defs
81 if fd.field_type is tracker_pb2.FieldTypes.ENUM_TYPE and
82 not fd.is_deleted}
83 non_masked_labels = tracker_bizobj.NonMaskedLabels(
84 template.labels, field_name_set)
85
86 field_views = tracker_views.MakeAllFieldValueViews(
87 config, template.labels, [], template.field_values, users_by_id,
88 phases=template.phases)
89 uneditable_fields = ezt.boolean(False)
90 for fv in field_views:
91 if permissions.CanEditValueForFieldDef(
92 mr.auth.effective_ids, mr.perms, mr.project, fv.field_def.field_def):
93 fv.is_editable = ezt.boolean(True)
94 else:
95 fv.is_editable = ezt.boolean(False)
96 uneditable_fields = ezt.boolean(True)
97
98 (prechecked_approvals, required_approval_ids,
99 initial_phases) = template_helpers.GatherApprovalsPageData(
100 template.approval_values, template.phases, config)
101
102 allow_edit = permissions.CanEditTemplate(
103 mr.auth.effective_ids, mr.perms, mr.project, template)
104
105 return {
106 'admin_tab_mode':
107 self._PROCESS_SUBTAB,
108 'allow_edit':
109 ezt.boolean(allow_edit),
110 'uneditable_fields':
111 uneditable_fields,
112 'new_template_form':
113 ezt.boolean(False),
114 'initial_members_only':
115 template_view.members_only,
116 'template_name':
117 template_view.name,
118 'initial_summary':
119 template_view.summary,
120 'initial_must_edit_summary':
121 template_view.summary_must_be_edited,
122 'initial_content':
123 template_view.content,
124 'initial_status':
125 template_view.status,
126 'initial_owner':
127 template_view.ownername,
128 'initial_owner_defaults_to_member':
129 template_view.owner_defaults_to_member,
130 'initial_components':
131 template_view.components,
132 'initial_component_required':
133 template_view.component_required,
134 'fields':
135 [
136 view for view in field_views
137 if view.field_def.type_name is not 'APPROVAL_TYPE'
138 ],
139 'initial_add_approvals':
140 ezt.boolean(prechecked_approvals),
141 'initial_phases':
142 initial_phases,
143 'approvals':
144 [
145 view for view in field_views
146 if view.field_def.type_name is 'APPROVAL_TYPE'
147 ],
148 'prechecked_approvals':
149 prechecked_approvals,
150 'required_approval_ids':
151 required_approval_ids,
152 'labels':
153 non_masked_labels,
154 'initial_admins':
155 template_view.admin_names,
156 }
157
158 def ProcessFormData(self, mr, post_data):
159 """Validate and store the contents of the issues tracker admin page.
160
161 Args:
162 mr: commonly used info parsed from the request.
163 post_data: HTML form data from the request.
164
165 Returns:
166 String URL to redirect the user to, or None if response was already sent.
167 """
168
169 config = self.services.config.GetProjectConfig(mr.cnxn, mr.project_id)
170 parsed = template_helpers.ParseTemplateRequest(post_data, config)
171 field_helpers.ShiftEnumFieldsIntoLabels(
172 parsed.labels, [], parsed.field_val_strs, [], config)
173 template = self.services.template.GetTemplateByName(mr.cnxn,
174 parsed.name, mr.project_id)
175 allow_edit = permissions.CanEditTemplate(
176 mr.auth.effective_ids, mr.perms, mr.project, template)
177 if not allow_edit:
178 raise permissions.PermissionException(
179 'User is not allowed edit this issue template.')
180
181 if 'deletetemplate' in post_data:
182 self.services.template.DeleteIssueTemplateDef(
183 mr.cnxn, mr.project_id, template.template_id)
184 return framework_helpers.FormatAbsoluteURL(
185 mr, urls.ADMIN_TEMPLATES, deleted=1, ts=int(time.time()))
186
187 (admin_ids, owner_id, component_ids,
188 field_values, phases,
189 approvals) = template_helpers.GetTemplateInfoFromParsed(
190 mr, self.services, parsed, config)
191
192 labels = [label for label in parsed.labels if label]
193 field_helpers.AssertCustomFieldsEditPerms(
194 mr, config, field_values, [], [], labels, [])
195 field_helpers.ApplyRestrictedDefaultValues(
196 mr, config, field_values, labels, template.field_values,
197 template.labels)
198
199 if mr.errors.AnyErrors():
200 field_views = tracker_views.MakeAllFieldValueViews(
201 config, [], [], field_values, {})
202
203 prechecked_approvals = template_helpers.GetCheckedApprovalsFromParsed(
204 parsed.approvals_to_phase_idx)
205
206 self.PleaseCorrect(
207 mr,
208 initial_members_only=ezt.boolean(parsed.members_only),
209 template_name=parsed.name,
210 initial_summary=parsed.summary,
211 initial_must_edit_summary=ezt.boolean(parsed.summary_must_be_edited),
212 initial_content=parsed.content,
213 initial_status=parsed.status,
214 initial_owner=parsed.owner_str,
215 initial_owner_defaults_to_member=ezt.boolean(
216 parsed.owner_defaults_to_member),
217 initial_components=', '.join(parsed.component_paths),
218 initial_component_required=ezt.boolean(parsed.component_required),
219 initial_admins=parsed.admin_str,
220 labels=parsed.labels,
221 fields=[view for view in field_views
222 if view.field_def.type_name is not 'APPROVAL_TYPE'],
223 initial_add_approvals=ezt.boolean(parsed.add_approvals),
224 initial_phases=[tracker_pb2.Phase(name=name) for name in
225 parsed.phase_names],
226 approvals=[view for view in field_views
227 if view.field_def.type_name is 'APPROVAL_TYPE'],
228 prechecked_approvals=prechecked_approvals,
229 required_approval_ids=parsed.required_approval_ids
230 )
231 return
232
233 self.services.template.UpdateIssueTemplateDef(
234 mr.cnxn, mr.project_id, template.template_id, name=parsed.name,
235 content=parsed.content, summary=parsed.summary,
236 summary_must_be_edited=parsed.summary_must_be_edited,
237 status=parsed.status, members_only=parsed.members_only,
238 owner_defaults_to_member=parsed.owner_defaults_to_member,
239 component_required=parsed.component_required, owner_id=owner_id,
240 labels=labels, component_ids=component_ids, admin_ids=admin_ids,
241 field_values=field_values, phases=phases, approval_values=approvals)
242
243 return framework_helpers.FormatAbsoluteURL(
244 mr, urls.TEMPLATE_DETAIL, template=template.name,
245 saved=1, ts=int(time.time()))