Skip to content

Commit

Permalink
Rework how external bug tracker integrations are discovered
Browse files Browse the repository at this point in the history
- All of them are now listed as dotted class paths under the
  EXTERNAL_BUG_TRACKERS setting
- DB migration will rename existing records
- tcms.issuetracker.types.from_name() is replaced with
  django.utils.module_loading.import_string() which returns the
  class at the end of a dotted path
- update documentation

Refs #1151 - will make overriding bug-tracker integration easier

Refs kiwitcms/github-app#25 - will open
the path to providing a custom GitHub Issues integration for the
GitHub App
  • Loading branch information
atodorov committed Jul 9, 2020
1 parent 44116ae commit 4d7f400
Show file tree
Hide file tree
Showing 13 changed files with 70 additions and 31 deletions.
9 changes: 9 additions & 0 deletions docs/source/admin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ Each bug tracker is given a name, URL, API credentials and integration type.
The extent of integration with 3rd party bug tracking systems is documented
in :mod:`tcms.issuetracker`.

.. important::

External bug tracker integration classes are defined as a dotted path list
in the ``EXTERNAL_BUG_TRACKERS`` setting, see :ref:`configuration`.
Plugins and Kiwi TCMS admins may override this setting to provide more
control and customized integration.

.. versionadded:: 8.5

.. important::

Details on what each field means can be found at
Expand Down
2 changes: 1 addition & 1 deletion tcms/issuetracker/tests/test_bugzilla.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def _fixture_setup(self):

bug_system = BugSystem.objects.create( # nosec:B106:hardcoded_password_funcarg
name='Dockerized Bugzilla',
tracker_type='Bugzilla',
tracker_type='tcms.issuetracker.types.Bugzilla',
base_url='http://bugtracker.kiwitcms.org/bugzilla/',
api_url='http://bugtracker.kiwitcms.org/bugzilla/xmlrpc.cgi',
api_username='[email protected]',
Expand Down
2 changes: 1 addition & 1 deletion tcms/issuetracker/tests/test_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def _fixture_setup(self):

bug_system = BugSystem.objects.create( # nosec:B106:hardcoded_password_funcarg
name='GitHub for kiwitcms/test-github-integration',
tracker_type='GitHub',
tracker_type='tcms.issuetracker.types.GitHub',
base_url='https://github.com/kiwitcms/test-github-integration',
api_password=os.getenv('GH_BUGTRACKER_INTEGRATION_TEST_API_TOKEN'),
)
Expand Down
4 changes: 2 additions & 2 deletions tcms/issuetracker/tests/test_gitlab_ee.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def _fixture_setup(self):

bug_system = BugSystem.objects.create( # nosec:B106:hardcoded_password_funcarg
name='GitLab-EE for root/kiwitcms',
tracker_type='Gitlab',
tracker_type='tcms.issuetracker.types.Gitlab',
base_url='http://bugtracker.kiwitcms.org/root/kiwitcms/',
api_url='http://bugtracker.kiwitcms.org',
api_password='ypCa3Dzb23o5nvsixwPA',
Expand All @@ -57,7 +57,7 @@ def test_details_for_public_url(self):
def test_details_for_private_url(self):
bug_system = BugSystem.objects.create( # nosec:B106:hardcoded_password_funcarg
name='Private GitLab for root/katinar',
tracker_type='Gitlab',
tracker_type='tcms.issuetracker.types.Gitlab',
base_url='http://bugtracker.kiwitcms.org/root/katinar/',
api_url='http://bugtracker.kiwitcms.org',
api_password='ypCa3Dzb23o5nvsixwPA',
Expand Down
2 changes: 1 addition & 1 deletion tcms/issuetracker/tests/test_jira.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def _fixture_setup(self):

bug_system = BugSystem.objects.create( # nosec:B106:hardcoded_password_funcarg
name='JIRA at kiwitcms.atlassian.net',
tracker_type='JIRA',
tracker_type='tcms.issuetracker.types.JIRA',
base_url='https://kiwitcms.atlassian.net',
api_username=os.getenv('JIRA_BUGTRACKER_INTEGRATION_API_USERNAME'),
api_password=os.getenv('JIRA_BUGTRACKER_INTEGRATION_API_TOKEN'),
Expand Down
2 changes: 1 addition & 1 deletion tcms/issuetracker/tests/test_kiwitcms.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def _fixture_setup(self):

bug_system = BugSystem.objects.create( # nosec:B106:hardcoded_password_funcarg
name='KiwiTCMS internal bug tracker',
tracker_type='KiwiTCMS',
tracker_type='tcms.issuetracker.types.KiwiTCMS',
base_url="https://%s" % Site.objects.get(id=settings.SITE_ID).domain,
# note: ^^^ this is https just because .get_full_url() default to that !
)
Expand Down
2 changes: 1 addition & 1 deletion tcms/issuetracker/tests/test_redmine.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def _fixture_setup(self):

bug_system = BugSystem.objects.create( # nosec:B106:hardcoded_password_funcarg
name='Redmine at kiwitcms.atlassian.net',
tracker_type='Redmine',
tracker_type='tcms.issuetracker.types.Redmine',
base_url='http://bugtracker.kiwitcms.org:3000',
api_username='admin',
api_password='admin',
Expand Down
10 changes: 0 additions & 10 deletions tcms/issuetracker/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,6 @@
from tcms.issuetracker.kiwitcms import KiwiTCMS # noqa, pylint: disable=unused-import


def from_name(name):
"""
Return the class which matches ``name`` if it exists inside this
module or raise an exception.
"""
if name not in globals():
raise NotImplementedError('IT of type %s is not supported' % name)
return globals()[name]


class JIRA(IssueTrackerType):
"""
Support for JIRA. Requires:
Expand Down
4 changes: 2 additions & 2 deletions tcms/rpc/api/bug.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
from django.core.cache import cache
from django.utils.module_loading import import_string
from django.utils.translation import gettext_lazy as _
from modernrpc.core import REQUEST_KEY, rpc_method

from tcms.issuetracker.types import from_name
from tcms.rpc.api.utils import tracker_from_url
from tcms.testcases.models import BugSystem
from tcms.testruns.models import TestExecution
Expand Down Expand Up @@ -62,7 +62,7 @@ def report(execution_id, tracker_id, **kwargs):

execution = TestExecution.objects.get(pk=execution_id)
bug_system = BugSystem.objects.get(pk=tracker_id)
tracker = from_name(bug_system.tracker_type)(bug_system)
tracker = import_string(bug_system.tracker_type)(bug_system)
if not tracker.is_adding_testcase_to_issue_disabled():
url = tracker.report_issue_from_testexecution(execution, request.user)
response = {'rc': 0, 'response': url}
Expand Down
7 changes: 4 additions & 3 deletions tcms/rpc/api/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Copyright (c) 2019 Alexander Todorov <[email protected]>
# Copyright (c) 2019-2020 Alexander Todorov <[email protected]>

# Licensed under the GPL 2.0: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html

from tcms.issuetracker.types import from_name
from django.utils.module_loading import import_string

from tcms.testcases.models import BugSystem


Expand All @@ -14,6 +15,6 @@ def tracker_from_url(url):
"""
for bug_system in BugSystem.objects.all():
if bug_system.base_url and url.startswith(bug_system.base_url):
return from_name(bug_system.tracker_type)(bug_system)
return import_string(bug_system.tracker_type)(bug_system)

return None
14 changes: 14 additions & 0 deletions tcms/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,20 @@
if 'tcms.bugs.apps.AppConfig' in INSTALLED_APPS:
MODERNRPC_METHODS_MODULES.append('tcms.bugs.api')

# This is a list of dotted class names providing integration with
# external bug trackers. Plugins and downstream installation can augment
# this list with their own integration classes!
# https://kiwitcms.readthedocs.io/en/latest/admin.html#configure-external-bug-trackers
EXTERNAL_BUG_TRACKERS = [
'tcms.issuetracker.types.Bugzilla',
'tcms.issuetracker.types.JIRA',
'tcms.issuetracker.types.GitHub',
'tcms.issuetracker.types.Gitlab',
'tcms.issuetracker.types.Redmine',
]
if 'tcms.bugs.apps.AppConfig' in INSTALLED_APPS:
EXTERNAL_BUG_TRACKERS.append('tcms.issuetracker.types.KiwiTCMS')

# Enable the administrator delete permission
# In another word it's set the admin to super user or not.
SET_ADMIN_AS_SUPERUSER = False
Expand Down
13 changes: 4 additions & 9 deletions tcms/testcases/admin.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# pylint: disable=no-self-use
import inspect

from django import forms
from django.conf import settings
from django.contrib import admin
from django.forms.widgets import Select
from django.http import HttpResponseRedirect
from django.urls import reverse

from tcms.core.history import ReadOnlyHistoryAdmin
from tcms.issuetracker import types
from tcms.testcases.models import BugSystem, Category, TestCase


Expand Down Expand Up @@ -50,13 +49,9 @@ def choices(self, _):

@staticmethod
def _types_as_choices():
trackers = []
for module_object in types.__dict__.values():
if inspect.isclass(module_object) and \
issubclass(module_object, types.IssueTrackerType) and \
module_object != types.IssueTrackerType: # noqa: E721
trackers.append(module_object.__name__)
return (('', ''), ) + tuple(zip(trackers, trackers))
return (('', ''), ) + tuple(
zip(settings.EXTERNAL_BUG_TRACKERS,
settings.EXTERNAL_BUG_TRACKERS))


class IssueTrackerTypeField(forms.ChoiceField):
Expand Down
30 changes: 30 additions & 0 deletions tcms/testcases/migrations/0014_update_issutracker_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from django.db import migrations


def forwards(apps, schema_editor):
BugSystem = apps.get_model('testcases', 'BugSystem')

for record in BugSystem.objects.all():
if record.tracker_type:
record.tracker_type = "tcms.issuetracker.types.%s" % record.tracker_type
record.save()


def backwards(apps, schema_editor):
BugSystem = apps.get_model('testcases', 'BugSystem')

for record in BugSystem.objects.all():
if record.tracker_type.startswith('tcms.issuetracker.types.'):
record.tracker_type = record.tracker_type.replace('tcms.issuetracker.types.', '')
record.save()


class Migration(migrations.Migration):

dependencies = [
('testcases', '0013_remove_autofield'),
]

operations = [
migrations.RunPython(forwards, backwards),
]

0 comments on commit 4d7f400

Please sign in to comment.