Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1 | # Copyright 2018 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. |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 4 | |
| 5 | """The TemplateService class providing methods for template persistence.""" |
| 6 | from __future__ import print_function |
| 7 | from __future__ import division |
| 8 | from __future__ import absolute_import |
| 9 | |
| 10 | import collections |
| 11 | import logging |
| 12 | |
| 13 | import settings |
| 14 | |
| 15 | from framework import exceptions |
| 16 | from framework import sql |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 17 | from mrproto import tracker_pb2 |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 18 | from services import caches |
| 19 | from services import project_svc |
| 20 | from tracker import tracker_bizobj |
| 21 | from tracker import tracker_constants |
| 22 | |
| 23 | |
| 24 | TEMPLATE_COLS = [ |
| 25 | 'id', 'project_id', 'name', 'content', 'summary', 'summary_must_be_edited', |
| 26 | 'owner_id', 'status', 'members_only', 'owner_defaults_to_member', |
| 27 | 'component_required'] |
| 28 | TEMPLATE2LABEL_COLS = ['template_id', 'label'] |
| 29 | TEMPLATE2COMPONENT_COLS = ['template_id', 'component_id'] |
| 30 | TEMPLATE2ADMIN_COLS = ['template_id', 'admin_id'] |
| 31 | TEMPLATE2FIELDVALUE_COLS = [ |
| 32 | 'template_id', 'field_id', 'int_value', 'str_value', 'user_id', |
| 33 | 'date_value', 'url_value'] |
| 34 | ISSUEPHASEDEF_COLS = ['id', 'name', 'rank'] |
| 35 | TEMPLATE2APPROVALVALUE_COLS = [ |
| 36 | 'approval_id', 'template_id', 'phase_id', 'status'] |
| 37 | |
| 38 | |
| 39 | TEMPLATE_TABLE_NAME = 'Template' |
| 40 | TEMPLATE2LABEL_TABLE_NAME = 'Template2Label' |
| 41 | TEMPLATE2ADMIN_TABLE_NAME = 'Template2Admin' |
| 42 | TEMPLATE2COMPONENT_TABLE_NAME = 'Template2Component' |
| 43 | TEMPLATE2FIELDVALUE_TABLE_NAME = 'Template2FieldValue' |
| 44 | ISSUEPHASEDEF_TABLE_NAME = 'IssuePhaseDef' |
| 45 | TEMPLATE2APPROVALVALUE_TABLE_NAME = 'Template2ApprovalValue' |
| 46 | |
| 47 | |
| 48 | class TemplateSetTwoLevelCache(caches.AbstractTwoLevelCache): |
| 49 | """Class to manage RAM and memcache for templates. |
| 50 | |
| 51 | Holds a dictionary of {project_id: templateset} key value pairs, |
| 52 | where a templateset is a list of all templates in a project. |
| 53 | """ |
| 54 | |
| 55 | def __init__(self, cache_manager, template_service): |
| 56 | super(TemplateSetTwoLevelCache, self).__init__( |
| 57 | cache_manager, 'project', prefix='templateset:', pb_class=None) |
| 58 | self.template_service = template_service |
| 59 | |
| 60 | def _MakeCache(self, cache_manager, kind, max_size=None): |
| 61 | """Make the RAM cache and register it with the cache_manager.""" |
| 62 | return caches.RamCache(cache_manager, kind, max_size=max_size) |
| 63 | |
| 64 | def FetchItems(self, cnxn, keys): |
| 65 | """On RAM and memcache miss, hit the database.""" |
| 66 | template_set_dict = {} |
| 67 | |
| 68 | for project_id in keys: |
| 69 | template_set_dict.setdefault(project_id, []) |
| 70 | template_rows = self.template_service.template_tbl.Select( |
| 71 | cnxn, cols=TEMPLATE_COLS, project_id=project_id, |
| 72 | order_by=[('name', [])]) |
| 73 | for (template_id, _project_id, template_name, _content, _summary, |
| 74 | _summary_must_be_edited, _owner_id, _status, members_only, |
| 75 | _owner_defaults_to_member, _component_required) in template_rows: |
| 76 | template_set_row = (template_id, template_name, members_only) |
| 77 | template_set_dict[project_id].append(template_set_row) |
| 78 | |
| 79 | return template_set_dict |
| 80 | |
| 81 | |
| 82 | class TemplateDefTwoLevelCache(caches.AbstractTwoLevelCache): |
| 83 | """Class to manage RAM and memcache for individual TemplateDef. |
| 84 | |
| 85 | Holds a dictionary of {template_id: TemplateDef} key value pairs. |
| 86 | """ |
| 87 | def __init__(self, cache_manager, template_service): |
| 88 | super(TemplateDefTwoLevelCache, self).__init__( |
| 89 | cache_manager, |
| 90 | 'template', |
| 91 | prefix='templatedef:', |
| 92 | pb_class=tracker_pb2.TemplateDef) |
| 93 | self.template_service = template_service |
| 94 | |
| 95 | def _MakeCache(self, cache_manager, kind, max_size=None): |
| 96 | """Make the RAM cache and register it with the cache_manager.""" |
| 97 | return caches.RamCache(cache_manager, kind, max_size=max_size) |
| 98 | |
| 99 | def FetchItems(self, cnxn, keys): |
| 100 | """On RAM and memcache miss, hit the database. |
| 101 | |
| 102 | Args: |
| 103 | cnxn: A MonorailConnection. |
| 104 | keys: A list of template IDs (ints). |
| 105 | |
| 106 | Returns: |
| 107 | A dict of {template_id: TemplateDef}. |
| 108 | """ |
| 109 | template_dict = {} |
| 110 | |
| 111 | # Fetch template rows and relations. |
| 112 | template_rows = self.template_service.template_tbl.Select( |
| 113 | cnxn, cols=TEMPLATE_COLS, id=keys, |
| 114 | order_by=[('name', [])]) |
| 115 | |
| 116 | template2label_rows = self.template_service.\ |
| 117 | template2label_tbl.Select( |
| 118 | cnxn, cols=TEMPLATE2LABEL_COLS, template_id=keys) |
| 119 | template2component_rows = self.template_service.\ |
| 120 | template2component_tbl.Select( |
| 121 | cnxn, cols=TEMPLATE2COMPONENT_COLS, template_id=keys) |
| 122 | template2admin_rows = self.template_service.template2admin_tbl.Select( |
| 123 | cnxn, cols=TEMPLATE2ADMIN_COLS, template_id=keys) |
| 124 | template2fieldvalue_rows = self.template_service.\ |
| 125 | template2fieldvalue_tbl.Select( |
| 126 | cnxn, cols=TEMPLATE2FIELDVALUE_COLS, template_id=keys) |
| 127 | template2approvalvalue_rows = self.template_service.\ |
| 128 | template2approvalvalue_tbl.Select( |
| 129 | cnxn, cols=TEMPLATE2APPROVALVALUE_COLS, template_id=keys) |
| 130 | phase_ids = [av_row[2] for av_row in template2approvalvalue_rows] |
| 131 | phase_rows = [] |
| 132 | if phase_ids: |
| 133 | phase_rows = self.template_service.issuephasedef_tbl.Select( |
| 134 | cnxn, cols=ISSUEPHASEDEF_COLS, id=list(set(phase_ids))) |
| 135 | |
| 136 | # Build TemplateDef with all related data. |
| 137 | for template_row in template_rows: |
| 138 | template = UnpackTemplate(template_row) |
| 139 | template_dict[template.template_id] = template |
| 140 | |
| 141 | for template2label_row in template2label_rows: |
| 142 | template_id, label = template2label_row |
| 143 | template = template_dict.get(template_id) |
| 144 | if template: |
| 145 | template.labels.append(label) |
| 146 | |
| 147 | for template2component_row in template2component_rows: |
| 148 | template_id, component_id = template2component_row |
| 149 | template = template_dict.get(template_id) |
| 150 | if template: |
| 151 | template.component_ids.append(component_id) |
| 152 | |
| 153 | for template2admin_row in template2admin_rows: |
| 154 | template_id, admin_id = template2admin_row |
| 155 | template = template_dict.get(template_id) |
| 156 | if template: |
| 157 | template.admin_ids.append(admin_id) |
| 158 | |
| 159 | for fv_row in template2fieldvalue_rows: |
| 160 | (template_id, field_id, int_value, str_value, user_id, |
| 161 | date_value, url_value) = fv_row |
| 162 | fv = tracker_bizobj.MakeFieldValue( |
| 163 | field_id, int_value, str_value, user_id, date_value, url_value, |
| 164 | False) |
| 165 | template = template_dict.get(template_id) |
| 166 | if template: |
| 167 | template.field_values.append(fv) |
| 168 | |
| 169 | phases_by_id = {} |
| 170 | for phase_row in phase_rows: |
| 171 | (phase_id, name, rank) = phase_row |
| 172 | phase = tracker_pb2.Phase( |
| 173 | phase_id=phase_id, name=name, rank=rank) |
| 174 | phases_by_id[phase_id] = phase |
| 175 | |
| 176 | # Note: there is no templateapproval2approver_tbl. |
| 177 | for av_row in template2approvalvalue_rows: |
| 178 | (approval_id, template_id, phase_id, status) = av_row |
| 179 | approval_value = tracker_pb2.ApprovalValue( |
| 180 | approval_id=approval_id, phase_id=phase_id, |
| 181 | status=tracker_pb2.ApprovalStatus(status.upper())) |
| 182 | template = template_dict.get(template_id) |
| 183 | if template: |
| 184 | template.approval_values.append(approval_value) |
| 185 | phase = phases_by_id.get(phase_id) |
| 186 | if phase and phase not in template.phases: |
| 187 | template_dict.get(template_id).phases.append(phase) |
| 188 | |
| 189 | return template_dict |
| 190 | |
| 191 | |
| 192 | class TemplateService(object): |
| 193 | |
| 194 | def __init__(self, cache_manager): |
| 195 | self.template_tbl = sql.SQLTableManager(TEMPLATE_TABLE_NAME) |
| 196 | self.template2label_tbl = sql.SQLTableManager(TEMPLATE2LABEL_TABLE_NAME) |
| 197 | self.template2component_tbl = sql.SQLTableManager( |
| 198 | TEMPLATE2COMPONENT_TABLE_NAME) |
| 199 | self.template2admin_tbl = sql.SQLTableManager(TEMPLATE2ADMIN_TABLE_NAME) |
| 200 | self.template2fieldvalue_tbl = sql.SQLTableManager( |
| 201 | TEMPLATE2FIELDVALUE_TABLE_NAME) |
| 202 | self.issuephasedef_tbl = sql.SQLTableManager( |
| 203 | ISSUEPHASEDEF_TABLE_NAME) |
| 204 | self.template2approvalvalue_tbl = sql.SQLTableManager( |
| 205 | TEMPLATE2APPROVALVALUE_TABLE_NAME) |
| 206 | |
| 207 | self.template_set_2lc = TemplateSetTwoLevelCache(cache_manager, self) |
| 208 | self.template_def_2lc = TemplateDefTwoLevelCache(cache_manager, self) |
| 209 | |
| 210 | def CreateDefaultProjectTemplates(self, cnxn, project_id): |
| 211 | """Create the default templates for a project. |
| 212 | |
| 213 | Used only when creating a new project. |
| 214 | |
| 215 | Args: |
| 216 | cnxn: A MonorailConnection instance. |
| 217 | project_id: The project ID under which to create the templates. |
| 218 | """ |
| 219 | for tpl in tracker_constants.DEFAULT_TEMPLATES: |
| 220 | tpl = tracker_bizobj.ConvertDictToTemplate(tpl) |
| 221 | self.CreateIssueTemplateDef(cnxn, project_id, tpl.name, tpl.content, |
| 222 | tpl.summary, tpl.summary_must_be_edited, tpl.status, tpl.members_only, |
| 223 | tpl.owner_defaults_to_member, tpl.component_required, tpl.owner_id, |
| 224 | tpl.labels, tpl.component_ids, tpl.admin_ids, tpl.field_values, |
| 225 | tpl.phases) |
| 226 | |
| 227 | def GetTemplateByName(self, cnxn, template_name, project_id): |
| 228 | """Retrieves a template by name and project_id. |
| 229 | |
| 230 | Args: |
| 231 | template_name (string): name of template. |
| 232 | project_id (int): ID of project template is under. |
| 233 | |
| 234 | Returns: |
| 235 | A Template PB if found, otherwise None. |
| 236 | """ |
| 237 | template_set = self.GetTemplateSetForProject(cnxn, project_id) |
| 238 | for tpl_id, name, _members_only in template_set: |
| 239 | if template_name == name: |
| 240 | return self.GetTemplateById(cnxn, tpl_id) |
| 241 | |
| 242 | def GetTemplateById(self, cnxn, template_id): |
| 243 | """Retrieves one template. |
| 244 | |
| 245 | Args: |
| 246 | template_id (int): ID of the template. |
| 247 | |
| 248 | Returns: |
| 249 | A TemplateDef PB if found, otherwise None. |
| 250 | """ |
| 251 | result_dict, _ = self.template_def_2lc.GetAll(cnxn, [template_id]) |
| 252 | try: |
| 253 | return result_dict[template_id] |
| 254 | except KeyError: |
| 255 | return None |
| 256 | |
| 257 | def GetTemplatesById(self, cnxn, template_ids): |
| 258 | """Retrieves one or more templates by ID. |
| 259 | |
| 260 | Args: |
| 261 | template_id (list<int>): IDs of the templates. |
| 262 | |
| 263 | Returns: |
| 264 | A list containing any found TemplateDef PBs. |
| 265 | """ |
| 266 | result_dict, _ = self.template_def_2lc.GetAll(cnxn, template_ids) |
| 267 | return list(result_dict.values()) |
| 268 | |
| 269 | def GetTemplateSetForProject(self, cnxn, project_id): |
| 270 | """Get the TemplateSet for a project.""" |
| 271 | result_dict, _ = self.template_set_2lc.GetAll(cnxn, [project_id]) |
| 272 | return result_dict[project_id] |
| 273 | |
| 274 | def GetProjectTemplates(self, cnxn, project_id): |
| 275 | """Gets all templates in a given project. |
| 276 | |
| 277 | Args: |
| 278 | cnxn: A MonorailConnection instance. |
| 279 | project_id: All templates for this project will be returned. |
| 280 | |
| 281 | Returns: |
| 282 | A list of TemplateDefs. |
| 283 | """ |
| 284 | template_set = self.GetTemplateSetForProject(cnxn, project_id) |
| 285 | template_ids = [row[0] for row in template_set] |
| 286 | return self.GetTemplatesById(cnxn, template_ids) |
| 287 | |
| 288 | def TemplatesWithComponent(self, cnxn, component_id): |
| 289 | """Returns all templates with the specified component. |
| 290 | |
| 291 | Args: |
| 292 | cnxn: connection to SQL database. |
| 293 | component_id: int component id. |
| 294 | |
| 295 | Returns: |
| 296 | A list of TemplateDefs. |
| 297 | """ |
| 298 | template2component_rows = self.template2component_tbl.Select( |
| 299 | cnxn, cols=['template_id'], component_id=component_id) |
| 300 | template_ids = [r[0] for r in template2component_rows] |
| 301 | return self.GetTemplatesById(cnxn, template_ids) |
| 302 | |
| 303 | def CreateIssueTemplateDef( |
| 304 | self, cnxn, project_id, name, content, summary, summary_must_be_edited, |
| 305 | status, members_only, owner_defaults_to_member, component_required, |
| 306 | owner_id=None, labels=None, component_ids=None, admin_ids=None, |
| 307 | field_values=None, phases=None, approval_values=None): |
| 308 | """Create a new issue template definition with the given info. |
| 309 | |
| 310 | Args: |
| 311 | cnxn: connection to SQL database. |
| 312 | project_id: int ID of the current project. |
| 313 | name: name of the new issue template. |
| 314 | content: string content of the issue template. |
| 315 | summary: string summary of the issue template. |
| 316 | summary_must_be_edited: True if the summary must be edited when this |
| 317 | issue template is used to make a new issue. |
| 318 | status: string default status of a new issue created with this template. |
| 319 | members_only: True if only members can view this issue template. |
| 320 | owner_defaults_to_member: True is issue owner should be set to member |
| 321 | creating the issue. |
| 322 | component_required: True if a component is required. |
| 323 | owner_id: user_id of default owner, if any. |
| 324 | labels: list of string labels for the new issue, if any. |
| 325 | component_ids: list of component_ids, if any. |
| 326 | admin_ids: list of admin_ids, if any. |
| 327 | field_values: list of FieldValue PBs, if any. |
| 328 | phases: list of Phase PBs, if any. |
| 329 | approval_values: list of ApprovalValue PBs, if any. |
| 330 | |
| 331 | Returns: |
| 332 | Integer template_id of the new issue template definition. |
| 333 | """ |
| 334 | template_id = self.template_tbl.InsertRow( |
| 335 | cnxn, project_id=project_id, name=name, content=content, |
| 336 | summary=summary, summary_must_be_edited=summary_must_be_edited, |
| 337 | owner_id=owner_id, status=status, members_only=members_only, |
| 338 | owner_defaults_to_member=owner_defaults_to_member, |
| 339 | component_required=component_required, commit=False) |
| 340 | |
| 341 | if labels: |
| 342 | self.template2label_tbl.InsertRows( |
| 343 | cnxn, TEMPLATE2LABEL_COLS, [(template_id, label) for label in labels], |
| 344 | commit=False) |
| 345 | if component_ids: |
| 346 | self.template2component_tbl.InsertRows( |
| 347 | cnxn, TEMPLATE2COMPONENT_COLS, [(template_id, c_id) for |
| 348 | c_id in component_ids], commit=False) |
| 349 | if admin_ids: |
| 350 | self.template2admin_tbl.InsertRows( |
| 351 | cnxn, TEMPLATE2ADMIN_COLS, [(template_id, admin_id) for |
| 352 | admin_id in admin_ids], commit=False) |
| 353 | if field_values: |
| 354 | self.template2fieldvalue_tbl.InsertRows( |
| 355 | cnxn, TEMPLATE2FIELDVALUE_COLS, [ |
| 356 | (template_id, fv.field_id, fv.int_value, fv.str_value, fv.user_id, |
| 357 | fv.date_value, fv.url_value) for fv in field_values], |
| 358 | commit=False) |
| 359 | |
| 360 | # current phase_ids in approval_values and phases are temporary and were |
| 361 | # assigned based on the order of the phases. These temporary phase_ids are |
| 362 | # used to keep track of which approvals belong to which phases and are |
| 363 | # updated once all phases have their real phase_ids returned from InsertRow. |
| 364 | phase_id_by_tmp = {} |
| 365 | if phases: |
| 366 | for phase in phases: |
| 367 | phase_id = self.issuephasedef_tbl.InsertRow( |
| 368 | cnxn, name=phase.name, rank=phase.rank, commit=False) |
| 369 | phase_id_by_tmp[phase.phase_id] = phase_id |
| 370 | |
| 371 | if approval_values: |
| 372 | self.template2approvalvalue_tbl.InsertRows( |
| 373 | cnxn, TEMPLATE2APPROVALVALUE_COLS, |
| 374 | [(av.approval_id, template_id, |
| 375 | phase_id_by_tmp.get(av.phase_id), av.status.name.lower()) |
| 376 | for av in approval_values], |
| 377 | commit=False) |
| 378 | |
| 379 | cnxn.Commit() |
| 380 | self.template_set_2lc.InvalidateKeys(cnxn, [project_id]) |
| 381 | return template_id |
| 382 | |
| 383 | def UpdateIssueTemplateDef( |
| 384 | self, cnxn, project_id, template_id, name=None, content=None, |
| 385 | summary=None, summary_must_be_edited=None, status=None, members_only=None, |
| 386 | owner_defaults_to_member=None, component_required=None, owner_id=None, |
| 387 | labels=None, component_ids=None, admin_ids=None, field_values=None, |
| 388 | phases=None, approval_values=None): |
| 389 | """Update an existing issue template definition with the given info. |
| 390 | |
| 391 | Args: |
| 392 | cnxn: connection to SQL database. |
| 393 | project_id: int ID of the current project. |
| 394 | template_id: int ID of the issue template to update. |
| 395 | name: updated name of the new issue template. |
| 396 | content: updated string content of the issue template. |
| 397 | summary: updated string summary of the issue template. |
| 398 | summary_must_be_edited: True if the summary must be edited when this |
| 399 | issue template is used to make a new issue. |
| 400 | status: updated string default status of a new issue created with this |
| 401 | template. |
| 402 | members_only: True if only members can view this issue template. |
| 403 | owner_defaults_to_member: True is issue owner should be set to member |
| 404 | creating the issue. |
| 405 | component_required: True if a component is required. |
| 406 | owner_id: updated user_id of default owner, if any. |
| 407 | labels: updated list of string labels for the new issue, if any. |
| 408 | component_ids: updated list of component_ids, if any. |
| 409 | admin_ids: updated list of admin_ids, if any. |
| 410 | field_values: updated list of FieldValue PBs, if any. |
| 411 | phases: updated list of Phase PBs, if any. |
| 412 | approval_values: updated list of ApprovalValue PBs, if any. |
| 413 | """ |
| 414 | new_values = {} |
| 415 | if name is not None: |
| 416 | new_values['name'] = name |
| 417 | if content is not None: |
| 418 | new_values['content'] = content |
| 419 | if summary is not None: |
| 420 | new_values['summary'] = summary |
| 421 | if summary_must_be_edited is not None: |
| 422 | new_values['summary_must_be_edited'] = bool(summary_must_be_edited) |
| 423 | if status is not None: |
| 424 | new_values['status'] = status |
| 425 | if members_only is not None: |
| 426 | new_values['members_only'] = bool(members_only) |
| 427 | if owner_defaults_to_member is not None: |
| 428 | new_values['owner_defaults_to_member'] = bool(owner_defaults_to_member) |
| 429 | if component_required is not None: |
| 430 | new_values['component_required'] = bool(component_required) |
| 431 | if owner_id is not None: |
| 432 | new_values['owner_id'] = owner_id |
| 433 | |
| 434 | self.template_tbl.Update(cnxn, new_values, id=template_id, commit=False) |
| 435 | |
| 436 | if labels is not None: |
| 437 | self.template2label_tbl.Delete( |
| 438 | cnxn, template_id=template_id, commit=False) |
| 439 | self.template2label_tbl.InsertRows( |
| 440 | cnxn, TEMPLATE2LABEL_COLS, [(template_id, label) for label in labels], |
| 441 | commit=False) |
| 442 | if component_ids is not None: |
| 443 | self.template2component_tbl.Delete( |
| 444 | cnxn, template_id=template_id, commit=False) |
| 445 | self.template2component_tbl.InsertRows( |
| 446 | cnxn, TEMPLATE2COMPONENT_COLS, [(template_id, c_id) for |
| 447 | c_id in component_ids], |
| 448 | commit=False) |
| 449 | if admin_ids is not None: |
| 450 | self.template2admin_tbl.Delete( |
| 451 | cnxn, template_id=template_id, commit=False) |
| 452 | self.template2admin_tbl.InsertRows( |
| 453 | cnxn, TEMPLATE2ADMIN_COLS, [(template_id, admin_id) for |
| 454 | admin_id in admin_ids], |
| 455 | commit=False) |
| 456 | if field_values is not None: |
| 457 | self.template2fieldvalue_tbl.Delete( |
| 458 | cnxn, template_id=template_id, commit=False) |
| 459 | self.template2fieldvalue_tbl.InsertRows( |
| 460 | cnxn, TEMPLATE2FIELDVALUE_COLS, [ |
| 461 | (template_id, fv.field_id, fv.int_value, fv.str_value, fv.user_id, |
| 462 | fv.date_value, fv.url_value) for fv in field_values], |
| 463 | commit=False) |
| 464 | |
| 465 | # we need to keep track of tmp phase_ids created at the servlet. |
| 466 | phase_id_by_tmp = {} |
| 467 | if phases is not None: |
| 468 | self.template2approvalvalue_tbl.Delete( |
| 469 | cnxn, template_id=template_id, commit=False) |
| 470 | for phase in phases: |
| 471 | phase_id = self.issuephasedef_tbl.InsertRow( |
| 472 | cnxn, name=phase.name, rank=phase.rank, commit=False) |
| 473 | phase_id_by_tmp[phase.phase_id] = phase_id |
| 474 | |
| 475 | self.template2approvalvalue_tbl.InsertRows( |
| 476 | cnxn, TEMPLATE2APPROVALVALUE_COLS, |
| 477 | [(av.approval_id, template_id, |
| 478 | phase_id_by_tmp.get(av.phase_id), av.status.name.lower()) |
| 479 | for av in approval_values], commit=False) |
| 480 | |
| 481 | cnxn.Commit() |
| 482 | self.template_set_2lc.InvalidateKeys(cnxn, [project_id]) |
| 483 | self.template_def_2lc.InvalidateKeys(cnxn, [template_id]) |
| 484 | |
| 485 | def DeleteIssueTemplateDef(self, cnxn, project_id, template_id): |
| 486 | """Delete the specified issue template definition.""" |
| 487 | self.template2label_tbl.Delete(cnxn, template_id=template_id, commit=False) |
| 488 | self.template2component_tbl.Delete( |
| 489 | cnxn, template_id=template_id, commit=False) |
| 490 | self.template2admin_tbl.Delete(cnxn, template_id=template_id, commit=False) |
| 491 | self.template2fieldvalue_tbl.Delete( |
| 492 | cnxn, template_id=template_id, commit=False) |
| 493 | self.template2approvalvalue_tbl.Delete( |
| 494 | cnxn, template_id=template_id, commit=False) |
| 495 | # We do not delete issuephasedef rows becuase these rows will be used by |
| 496 | # issues that were created with this template. template2approvalvalue rows |
| 497 | # can be deleted because those rows are copied over to issue2approvalvalue |
| 498 | # during issue creation. |
| 499 | self.template_tbl.Delete(cnxn, id=template_id, commit=False) |
| 500 | |
| 501 | cnxn.Commit() |
| 502 | self.template_set_2lc.InvalidateKeys(cnxn, [project_id]) |
| 503 | self.template_def_2lc.InvalidateKeys(cnxn, [template_id]) |
| 504 | |
| 505 | def ExpungeProjectTemplates(self, cnxn, project_id): |
| 506 | template_id_rows = self.template_tbl.Select( |
| 507 | cnxn, cols=['id'], project_id=project_id) |
| 508 | template_ids = [row[0] for row in template_id_rows] |
| 509 | self.template2label_tbl.Delete(cnxn, template_id=template_ids) |
| 510 | self.template2component_tbl.Delete(cnxn, template_id=template_ids) |
| 511 | # TODO(3816): Delete all other relations here. |
| 512 | self.template_tbl.Delete(cnxn, project_id=project_id) |
| 513 | |
| 514 | def ExpungeUsersInTemplates(self, cnxn, user_ids, limit=None): |
| 515 | """Wipes a user from the templates system. |
| 516 | |
| 517 | This method will not commit the operation. This method will |
| 518 | not make changes to in-memory data. |
| 519 | """ |
| 520 | self.template2admin_tbl.Delete( |
| 521 | cnxn, admin_id=user_ids, commit=False, limit=limit) |
| 522 | self.template2fieldvalue_tbl.Delete( |
| 523 | cnxn, user_id=user_ids, commit=False, limit=limit) |
| 524 | # template_tbl's owner_id does not reference User. All appropriate rows |
| 525 | # should be deleted before rows can be safely deleted from User. No limit |
| 526 | # will be applied. |
| 527 | self.template_tbl.Update( |
| 528 | cnxn, {'owner_id': None}, owner_id=user_ids, commit=False) |
| 529 | |
| 530 | |
| 531 | def UnpackTemplate(template_row): |
| 532 | """Partially construct a template object using info from a DB row.""" |
| 533 | (template_id, _project_id, name, content, summary, |
| 534 | summary_must_be_edited, owner_id, status, |
| 535 | members_only, owner_defaults_to_member, component_required) = template_row |
| 536 | template = tracker_pb2.TemplateDef() |
| 537 | template.template_id = template_id |
| 538 | template.name = name |
| 539 | template.content = content |
| 540 | template.summary = summary |
| 541 | template.summary_must_be_edited = bool( |
| 542 | summary_must_be_edited) |
| 543 | template.owner_id = owner_id or 0 |
| 544 | template.status = status |
| 545 | template.members_only = bool(members_only) |
| 546 | template.owner_defaults_to_member = bool(owner_defaults_to_member) |
| 547 | template.component_required = bool(component_required) |
| 548 | |
| 549 | return template |