From 16aadc64a1d2e7334c7facbca3c1e902643246af Mon Sep 17 00:00:00 2001 From: Victor Seva Date: Tue, 13 Sep 2022 17:25:36 +0200 Subject: [PATCH] MT#33006 repoapi: support mantis * tracker app, move everything related to WF or Mantis there * move settings to tracker: - REPOAPI_TRACKER -> TRACKER_PROVIDER - MANTIS_* -> TRACKER_MANTIS_* - WORKFRONT_* -> TRACKER_WORKFRONT_* Change-Id: If3eba4e86ec90d6cb4259104d0f474e936f70d43 --- debian/repoapi.install | 1 + hotfix/conf.py | 7 -- hotfix/models.py | 74 +++--------- hotfix/test/test_hotfix_released.py | 28 ++--- hotfix/test/test_utils.py | 8 +- repoapi/admin.py | 20 ++++ repoapi/apps.py | 13 +-- repoapi/conf.py | 9 -- repoapi/fixtures/mantis_note_add.json | 85 ++++++++++++++ repoapi/migrations/0013_mantisnoteinfo.py | 35 ++++++ repoapi/models/__init__.py | 1 + repoapi/models/wni.py | 56 ++++++--- repoapi/settings/common.py | 6 +- repoapi/settings/prod.py | 7 +- repoapi/settings/test.py | 7 +- repoapi/signals.py | 34 +++--- repoapi/tasks.py | 4 +- repoapi/test/base.py | 4 +- repoapi/test/test_model_wni.py | 65 +++++++++++ repoapi/test/test_workfrontnote.py | 48 +++----- repoapi/utils.py | 75 ------------- tracker/__init__.py | 0 tracker/apps.py | 23 ++++ tracker/conf.py | 46 ++++++++ {hotfix => tracker}/exceptions.py | 4 + tracker/fixtures/mantis_issue.json | 1 + tracker/migrations/__init__.py | 0 tracker/models.py | 76 +++++++++++++ tracker/test/__init__.py | 0 tracker/test/test_utils.py | 86 ++++++++++++++ tracker/utils.py | 131 ++++++++++++++++++++++ 31 files changed, 697 insertions(+), 257 deletions(-) create mode 100644 repoapi/fixtures/mantis_note_add.json create mode 100644 repoapi/migrations/0013_mantisnoteinfo.py create mode 100644 repoapi/test/test_model_wni.py create mode 100644 tracker/__init__.py create mode 100644 tracker/apps.py create mode 100644 tracker/conf.py rename {hotfix => tracker}/exceptions.py (95%) create mode 100644 tracker/fixtures/mantis_issue.json create mode 100644 tracker/migrations/__init__.py create mode 100644 tracker/models.py create mode 100644 tracker/test/__init__.py create mode 100644 tracker/test/test_utils.py create mode 100644 tracker/utils.py diff --git a/debian/repoapi.install b/debian/repoapi.install index 0b199f0..0681948 100644 --- a/debian/repoapi.install +++ b/debian/repoapi.install @@ -9,3 +9,4 @@ repoapi usr/share/repoapi repoapi.ini etc/uwsgi/apps-available requirements usr/share/repoapi static_media usr/share/repoapi +tracker usr/share/repoapi diff --git a/hotfix/conf.py b/hotfix/conf.py index ef87bdd..d018fd7 100644 --- a/hotfix/conf.py +++ b/hotfix/conf.py @@ -15,15 +15,8 @@ from django.conf import settings # noqa from appconf import AppConf -from repoapi.conf import Tracker - class HotfixConf(AppConf): - REGEX = { - Tracker.NONE: r"#(\d+)", - Tracker.WORKFRONT: r"TT#(\d+)", - Tracker.MANTIS: r"MT#(\d+)", - } ARTIFACT = "debian_changelog.txt" class Meta: diff --git a/hotfix/models.py b/hotfix/models.py index 1d178ab..1dea28c 100644 --- a/hotfix/models.py +++ b/hotfix/models.py @@ -15,19 +15,20 @@ import re from django.db import models -from natsort import humansorted from .conf import settings -from .conf import Tracker -from .exceptions import TrackerNotDefined +from tracker.conf import Tracker +from tracker.exceptions import TrackerNotDefined +from tracker.models import MantisInfo +from tracker.models import TrackerInfo +from tracker.models import WorkfrontInfo hotfix_re_release = re.compile(r".+~(mr[0-9]+\.[0-9]+\.[0-9]+.[0-9]+)$") -class NoteInfo(models.Model): +class NoteInfo(TrackerInfo): projectname = models.CharField(max_length=50, null=False) version = models.CharField(max_length=50, null=False) - tracker_re = re.compile(settings.HOTFIX_REGEX[Tracker.NONE]) class Meta: abstract = True @@ -44,9 +45,9 @@ class NoteInfo(models.Model): @staticmethod def get_model(): - if settings.REPOAPI_TRACKER == Tracker.MANTIS: + if settings.TRACKER_PROVIDER == Tracker.MANTIS: return MantisNoteInfo - elif settings.REPOAPI_TRACKER == Tracker.WORKFRONT: + elif settings.TRACKER_PROVIDER == Tracker.WORKFRONT: return WorkfrontNoteInfo return NoteInfo @@ -68,73 +69,32 @@ class NoteInfo(models.Model): @classmethod def get_or_create(cls, defaults=None, **kwargs): field_id = kwargs.pop("field_id") - if settings.REPOAPI_TRACKER == Tracker.MANTIS: + if settings.TRACKER_PROVIDER == Tracker.MANTIS: model = MantisNoteInfo kwargs["mantis_id"] = field_id - elif settings.REPOAPI_TRACKER == Tracker.WORKFRONT: + elif settings.TRACKER_PROVIDER == Tracker.WORKFRONT: model = WorkfrontNoteInfo kwargs["workfront_id"] = field_id else: raise TrackerNotDefined() return model.objects.get_or_create(defaults, **kwargs) - @classmethod - def getIds(cls, change): - """ - parses text searching for tracker occurrences - returns a list of IDs - """ - if change: - res = cls.tracker_re.findall(change) - return set(res) - else: - return set() - - -class WorkfrontNoteInfo(NoteInfo): - workfront_id = models.CharField(max_length=50, null=False) - tracker_re = re.compile(settings.HOTFIX_REGEX[Tracker.WORKFRONT]) +class WorkfrontNoteInfo(NoteInfo, WorkfrontInfo): class Meta: unique_together = ["workfront_id", "projectname", "version"] - def send(self, msg: str): - from repoapi import utils - - utils.workfront_note_send(self.workfront_id, msg) - def set_target_release(self): - from repoapi import utils - - utils.workfront_set_release_target( - self.workfront_id, self.target_release + return super(WorkfrontNoteInfo, self).set_target_release( + self.target_release ) -class MantisNoteInfo(NoteInfo): - mantis_id = models.CharField(max_length=50, null=False) - tracker_re = re.compile(settings.HOTFIX_REGEX[Tracker.MANTIS]) - +class MantisNoteInfo(NoteInfo, MantisInfo): class Meta: unique_together = ["mantis_id", "projectname", "version"] - def send(self, msg: str): - from repoapi import utils - - utils.mantis_note_send(self.mantis_id, msg) - def set_target_release(self): - """reconstruct value without asking for previous value""" - from repoapi import utils - - qs = MantisNoteInfo.objects.filter(mantis_id=self.mantis_id) - values = qs.values_list("version", flat=True) - versions = set() - for val in values: - target_release = self.get_target_release(val) - if target_release: - versions.add(target_release) - if len(versions) > 0: - utils.mantis_set_release_target( - self.mantis_id, ",".join(humansorted(versions)) - ) + return super(MantisNoteInfo, self).set_target_release( + self.target_release + ) diff --git a/hotfix/test/test_hotfix_released.py b/hotfix/test/test_hotfix_released.py index fdce2d4..9bd8465 100644 --- a/hotfix/test/test_hotfix_released.py +++ b/hotfix/test/test_hotfix_released.py @@ -20,9 +20,9 @@ from django.test import override_settings from hotfix import models from hotfix import utils -from hotfix.conf import Tracker from repoapi.models import JenkinsBuildInfo from repoapi.test.base import BaseTest +from tracker.conf import Tracker debian_changelog = """ngcp-fake (3.8.7.4+0~mr3.8.7.4) unstable; urgency=medium @@ -57,11 +57,11 @@ class TestHotfixReleased(BaseTest): } return defaults - @override_settings(REPOAPI_TRACKER=Tracker.WORKFRONT) + @override_settings(TRACKER_PROVIDER=Tracker.WORKFRONT) @patch("builtins.open", mock_open(read_data=debian_changelog)) @patch("repoapi.utils.dlfile") - @patch("repoapi.utils.workfront_set_release_target") - @patch("repoapi.utils.workfront_note_send") + @patch("tracker.utils.workfront_set_release_target") + @patch("tracker.utils.workfront_note_send") def test_hotfixreleased_wf(self, wns, wsrt, dlfile): param = self.get_defaults() jbi = JenkinsBuildInfo.objects.create(**param) @@ -92,11 +92,11 @@ class TestHotfixReleased(BaseTest): any_order=True, ) - @override_settings(REPOAPI_TRACKER=Tracker.MANTIS) + @override_settings(TRACKER_PROVIDER=Tracker.MANTIS) @patch("builtins.open", mock_open(read_data=debian_changelog)) @patch("repoapi.utils.dlfile") - @patch("repoapi.utils.mantis_set_release_target") - @patch("repoapi.utils.mantis_note_send") + @patch("tracker.utils.mantis_set_release_target") + @patch("tracker.utils.mantis_note_send") def test_hotfixreleased_mantis(self, mns, msrt, dlfile): param = self.get_defaults() jbi = JenkinsBuildInfo.objects.create(**param) @@ -127,25 +127,21 @@ class TestHotfixReleased(BaseTest): any_order=True, ) - @override_settings(REPOAPI_TRACKER=Tracker.MANTIS) + @override_settings(TRACKER_PROVIDER=Tracker.MANTIS) @patch("builtins.open", mock_open(read_data=debian_changelog)) @patch("repoapi.utils.dlfile") - @patch("repoapi.utils.mantis_set_release_target") - @patch("repoapi.utils.mantis_note_send") + @patch("tracker.utils.mantis_set_release_target") + @patch("tracker.utils.mantis_note_send") def test_hotfixreleased_mantis_versions(self, mns, msrt, dlfile): param = self.get_defaults() jbi = JenkinsBuildInfo.objects.create(**param) projectname = "fake" version = "3.8.7.4+0~mr3.8.7.4" - other_version = "5.8.7.4+0~mr5.8.7.4" - gri = models.MantisNoteInfo.objects.create( - mantis_id="8989", projectname=projectname, version=other_version - ) utils.process_hotfix(str(jbi), jbi.projectname, "/tmp/fake.txt") gri = models.MantisNoteInfo.objects.filter( mantis_id="8989", projectname=projectname ) - self.assertEqual(gri.count(), 2) + self.assertEqual(gri.count(), 1) msg = "hotfix %s.git %s triggered" % (projectname, version) calls = [ call("8989", msg), @@ -159,7 +155,7 @@ class TestHotfixReleased(BaseTest): mns.assert_has_calls(calls, any_order=True) msrt.assert_has_calls( [ - call("8989", "mr3.8.7.4,mr5.8.7.4"), + call("8989", "mr3.8.7.4"), call("21499", "mr3.8.7.4"), ], any_order=True, diff --git a/hotfix/test/test_utils.py b/hotfix/test/test_utils.py index b2df461..1490025 100644 --- a/hotfix/test/test_utils.py +++ b/hotfix/test/test_utils.py @@ -20,7 +20,7 @@ from django.test import SimpleTestCase from natsort import humansorted from hotfix import utils -from hotfix.conf import Tracker +from tracker.conf import Tracker debian_changelog = """ngcp-fake (3.8.7.4+0~mr3.8.7.4) unstable; urgency=medium [ Kirill Solomko ] @@ -34,7 +34,7 @@ debian_changelog = """ngcp-fake (3.8.7.4+0~mr3.8.7.4) unstable; urgency=medium class TestUtils(SimpleTestCase): - @override_settings(REPOAPI_TRACKER=Tracker.NONE) + @override_settings(TRACKER_PROVIDER=Tracker.NONE) @patch("builtins.open", mock_open(read_data=debian_changelog)) def test_parse_changelog_none(self): ids, changelog = utils.parse_changelog("/tmp/fake.txt") @@ -42,7 +42,7 @@ class TestUtils(SimpleTestCase): self.assertEqual(changelog.full_version, "3.8.7.4+0~mr3.8.7.4") self.assertEqual(changelog.package, "ngcp-fake") - @override_settings(REPOAPI_TRACKER=Tracker.WORKFRONT) + @override_settings(TRACKER_PROVIDER=Tracker.WORKFRONT) @patch("builtins.open", mock_open(read_data=debian_changelog)) def test_parse_changelog_wf(self): ids, changelog = utils.parse_changelog("/tmp/fake.txt") @@ -50,7 +50,7 @@ class TestUtils(SimpleTestCase): self.assertEqual(changelog.full_version, "3.8.7.4+0~mr3.8.7.4") self.assertEqual(changelog.package, "ngcp-fake") - @override_settings(REPOAPI_TRACKER=Tracker.MANTIS) + @override_settings(TRACKER_PROVIDER=Tracker.MANTIS) @patch("builtins.open", mock_open(read_data=debian_changelog)) def test_parse_changelog_mantis(self): ids, changelog = utils.parse_changelog("/tmp/fake.txt") diff --git a/repoapi/admin.py b/repoapi/admin.py index 7e64f76..df6b89a 100644 --- a/repoapi/admin.py +++ b/repoapi/admin.py @@ -38,3 +38,23 @@ class JenkinsBuildInfoAdmin(ImportExportModelAdmin): @admin.register(models.GerritRepoInfo) class GerritRepoInfoAdmin(ImportExportModelAdmin): resource_class = GerritRepoInfoResource + + +class WorkfrontNoteInfoResource(resources.ModelResource): + class Meta: + model = models.WorkfrontNoteInfo + + +@admin.register(models.WorkfrontNoteInfo) +class WorkfrontNoteInfoAdmin(ImportExportModelAdmin): + resource_class = WorkfrontNoteInfoResource + + +class MantisNoteInfoResource(resources.ModelResource): + class Meta: + model = models.MantisNoteInfo + + +@admin.register(models.MantisNoteInfo) +class MantisNoteInfoAdmin(ImportExportModelAdmin): + resource_class = MantisNoteInfoResource diff --git a/repoapi/apps.py b/repoapi/apps.py index 3a37d8b..49102b7 100644 --- a/repoapi/apps.py +++ b/repoapi/apps.py @@ -20,14 +20,13 @@ class RepoAPIConfig(AppConfig): name = "repoapi" def ready(self): - from .conf import settings, Tracker + from .conf import settings # noqa # Implicitly connect a signal handlers decorated with @receiver. from . import signals - if settings.REPOAPI_TRACKER == Tracker.WORKFRONT: - post_save.connect( - signals.workfront_note_manage, - sender="repoapi.JenkinsBuildInfo", - dispatch_uid="workfront_note_manage", - ) + post_save.connect( + signals.note_manager, + sender="repoapi.JenkinsBuildInfo", + dispatch_uid="tracker_note_manager", + ) diff --git a/repoapi/conf.py b/repoapi/conf.py index 15b636d..cf8ddce 100644 --- a/repoapi/conf.py +++ b/repoapi/conf.py @@ -12,23 +12,14 @@ # # You should have received a copy of the GNU General Public License along # with this program. If not, see . -from enum import Enum, unique from django.conf import settings # noqa from appconf import AppConf -@unique -class Tracker(Enum): - NONE = "None" - MANTIS = "Mantis" - WORKFRONT = "WorkFront" - - class RepoAPIConf(AppConf): ARTIFACT_JOB_REGEX = [ ".*-repos$", ] - TRACKER = Tracker.MANTIS class Meta: prefix = "repoapi" diff --git a/repoapi/fixtures/mantis_note_add.json b/repoapi/fixtures/mantis_note_add.json new file mode 100644 index 0000000..73be20a --- /dev/null +++ b/repoapi/fixtures/mantis_note_add.json @@ -0,0 +1,85 @@ +{'note': {'id': 406459, 'reporter': {'id': 281, 'name': 'vseva', 'real_name': 'Victor Seva (Sipwise)', 'email': 'vseva@sipwise.com' + }, 'text': 'hotfix fake_project.git fake_version triggered', 'view_state': {'id': 50, 'name': 'private', 'label': 'private' + }, 'attachments': [], 'type': 'note', 'created_at': '2022-09-13T12: 10: 22+02: 00', 'updated_at': '2022-09-13T12: 10: 22+02: 00' + }, 'issue': {'id': 13503, 'summary': 'Test custom support queue flag', 'description': 'is_support_queue should be true', 'project': {'id': 3, 'name': 'test project' + }, 'category': {'id': 1, 'name': 'None' + }, 'reporter': {'id': 125, 'name': 'gfuchs', 'real_name': 'Gernot Fuchs' + }, 'handler': {'id': 125, 'name': 'gfuchs', 'real_name': 'Gernot Fuchs' + }, 'status': {'id': 80, 'name': 'resolved', 'label': 'resolved', 'color': '#d2f5b0' + }, 'resolution': {'id': 20, 'name': 'fixed', 'label': 'fixed' + }, 'view_state': {'id': 10, 'name': 'public', 'label': 'public' + }, 'priority': {'id': 30, 'name': 'P3-normal', 'label': 'P3-normal' + }, 'severity': {'id': 50, 'name': 'minor', 'label': 'minor' + }, 'reproducibility': {'id': 70, 'name': 'have not tried', 'label': 'have not tried' + }, 'sticky': False, 'created_at': '2015-06-19T11: 01: 42+02: 00', 'updated_at': '2022-09-13T12: 10: 22+02: 00', 'notes': [ + {'id': 406459, 'reporter': {'id': 281, 'name': 'vseva', 'real_name': 'Victor Seva (Sipwise)', 'email': 'vseva@sipwise.com' + }, 'text': 'hotfix fake_project.git fake_version triggered', 'view_state': {'id': 50, 'name': 'private', 'label': 'private' + }, 'attachments': [], 'type': 'note', 'created_at': '2022-09-13T12: 10: 22+02: 00', 'updated_at': '2022-09-13T12: 10: 22+02: 00' + } + ], 'custom_fields': [ + {'field': {'id': 11, 'name': 'Classification' + }, 'value': '' + }, + {'field': {'id': 23, 'name': 'Dev Related Tickets' + }, 'value': '' + }, + {'field': {'id': 7, 'name': 'Log Time' + }, 'value': '0.5' + }, + {'field': {'id': 24, 'name': 'NGCP Version' + }, 'value': '' + }, + {'field': {'id': 38, 'name': 'Product Version' + }, 'value': '' + }, + {'field': {'id': 52, 'name': 'Status Details' + }, 'value': '' + } + ], 'monitors': [ + {'id': 125, 'name': 'gfuchs', 'real_name': 'Gernot Fuchs' + } + ], 'history': [ + {'created_at': '2015-06-19T11: 01: 42+02: 00', 'user': {'id': 125, 'name': 'gfuchs', 'real_name': 'Gernot Fuchs' + }, 'type': {'id': 1, 'name': 'issue-new' + }, 'message': 'New Issue' + }, + {'created_at': '2015-06-19T11: 01: 42+02: 00', 'user': {'id': 125, 'name': 'gfuchs', 'real_name': 'Gernot Fuchs' + }, 'type': {'id': 12, 'name': 'monitor-added' + }, 'old_value': '125', 'new_value': '', 'message': 'Issue Monitored: Gernot Fuchs' + }, + {'created_at': '2015-06-19T11: 05: 01+02: 00', 'user': {'id': 125, 'name': 'gfuchs', 'real_name': 'Gernot Fuchs' + }, 'field': {'name': 'handler', 'label': 'Assigned To' + }, 'type': {'id': 0, 'name': 'field-updated' + }, 'old_value': {'id': 0 + }, 'new_value': {'id': 125, 'name': 'gfuchs', 'real_name': 'Gernot Fuchs' + }, 'message': 'Assigned To', 'change': ' => Gernot Fuchs' + }, + {'created_at': '2015-06-19T11: 05: 01+02: 00', 'user': {'id': 125, 'name': 'gfuchs', 'real_name': 'Gernot Fuchs' + }, 'field': {'name': 'status', 'label': 'Status' + }, 'type': {'id': 0, 'name': 'field-updated' + }, 'old_value': {'id': 10, 'name': 'new', 'label': 'new', 'color': '#fcbdbd' + }, 'new_value': {'id': 50, 'name': 'assigned', 'label': 'assigned', 'color': '#c2dfff' + }, 'message': 'Status', 'change': 'new => assigned' + }, + {'created_at': '2015-06-19T11: 05: 09+02: 00', 'user': {'id': 125, 'name': 'gfuchs', 'real_name': 'Gernot Fuchs' + }, 'field': {'name': 'status', 'label': 'Status' + }, 'type': {'id': 0, 'name': 'field-updated' + }, 'old_value': {'id': 50, 'name': 'assigned', 'label': 'assigned', 'color': '#c2dfff' + }, 'new_value': {'id': 80, 'name': 'resolved', 'label': 'resolved', 'color': '#d2f5b0' + }, 'message': 'Status', 'change': 'assigned => resolved' + }, + {'created_at': '2015-06-19T11: 05: 09+02: 00', 'user': {'id': 125, 'name': 'gfuchs', 'real_name': 'Gernot Fuchs' + }, 'field': {'name': 'resolution', 'label': 'Resolution' + }, 'type': {'id': 0, 'name': 'field-updated' + }, 'old_value': {'id': 10, 'name': 'open', 'label': 'open' + }, 'new_value': {'id': 20, 'name': 'fixed', 'label': 'fixed' + }, 'message': 'Resolution', 'change': 'open => fixed' + }, + {'created_at': '2022-09-13T12: 10: 22+02: 00', 'user': {'id': 281, 'name': 'vseva', 'real_name': 'Victor Seva (Sipwise)' + }, 'type': {'id': 2, 'name': 'note-added' + }, 'note': {'id': 406459 + }, 'message': 'Note Added: 0406459' + } + ] + } +} \ No newline at end of file diff --git a/repoapi/migrations/0013_mantisnoteinfo.py b/repoapi/migrations/0013_mantisnoteinfo.py new file mode 100644 index 0000000..68b6120 --- /dev/null +++ b/repoapi/migrations/0013_mantisnoteinfo.py @@ -0,0 +1,35 @@ +# Generated by Django 3.2.15 on 2022-09-13 17:33 +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + + dependencies = [ + ("repoapi", "0012_ldap_grp_perms"), + ] + + operations = [ + migrations.CreateModel( + name="MantisNoteInfo", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("mantis_id", models.CharField(max_length=50)), + ("gerrit_change", models.CharField(max_length=50)), + ("eventtype", models.CharField(max_length=50)), + ], + options={ + "unique_together": { + ("mantis_id", "gerrit_change", "eventtype") + }, + }, + ), + ] diff --git a/repoapi/models/__init__.py b/repoapi/models/__init__.py index d551aac..897591e 100644 --- a/repoapi/models/__init__.py +++ b/repoapi/models/__init__.py @@ -14,4 +14,5 @@ # with this program. If not, see . from .gri import GerritRepoInfo # noqa from .jbi import JenkinsBuildInfo # noqa +from .wni import MantisNoteInfo # noqa from .wni import WorkfrontNoteInfo # noqa diff --git a/repoapi/models/wni.py b/repoapi/models/wni.py index 9bf0d1b..ecd7b8e 100644 --- a/repoapi/models/wni.py +++ b/repoapi/models/wni.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 The Sipwise Team - http://sipwise.com +# Copyright (C) 2015-2022 The Sipwise Team - http://sipwise.com # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free @@ -16,30 +16,44 @@ import re from django.db import models -workfront_re = re.compile(r"TT#(\d+)") -workfront_re_branch = re.compile(r"^mr[0-9]+\.[0-9]+\.[0-9]+$") +from repoapi.conf import settings +from tracker.conf import Tracker +from tracker.exceptions import TrackerNotDefined +from tracker.models import MantisInfo +from tracker.models import TrackerInfo +from tracker.models import WorkfrontInfo + +re_branch = re.compile(r"^mr[0-9]+\.[0-9]+\.[0-9]+$") commit_re = re.compile(r"^(\w{7}) ") -class WorkfrontNoteInfo(models.Model): - workfront_id = models.CharField(max_length=50, null=False) +class NoteInfo(TrackerInfo): gerrit_change = models.CharField(max_length=50, null=False) eventtype = models.CharField(max_length=50, null=False) class Meta: - unique_together = ["workfront_id", "gerrit_change", "eventtype"] + abstract = True @staticmethod - def getIds(git_comment): - """ - parses git_commit_msg searching for Workfront TT# occurrences - returns a list of IDs - """ - if git_comment: - res = workfront_re.findall(git_comment) - return set(res) + def get_model(): + if settings.TRACKER_PROVIDER == Tracker.MANTIS: + return MantisNoteInfo + elif settings.TRACKER_PROVIDER == Tracker.WORKFRONT: + return WorkfrontNoteInfo + return NoteInfo + + @classmethod + def get_or_create(cls, defaults=None, **kwargs): + field_id = kwargs.pop("field_id") + if settings.TRACKER_PROVIDER == Tracker.MANTIS: + model = MantisNoteInfo + kwargs["mantis_id"] = field_id + elif settings.TRACKER_PROVIDER == Tracker.WORKFRONT: + model = WorkfrontNoteInfo + kwargs["workfront_id"] = field_id else: - return set() + raise TrackerNotDefined() + return model.objects.get_or_create(defaults, **kwargs) @staticmethod def getCommit(git_comment): @@ -52,4 +66,14 @@ class WorkfrontNoteInfo(models.Model): return res.group(1) def __str__(self): - return "%s:%s" % (self.workfront_id, self.gerrit_change) + return "%s:%s" % (self.field_id, self.gerrit_change) + + +class WorkfrontNoteInfo(NoteInfo, WorkfrontInfo): + class Meta: + unique_together = ["workfront_id", "gerrit_change", "eventtype"] + + +class MantisNoteInfo(NoteInfo, MantisInfo): + class Meta: + unique_together = ["mantis_id", "gerrit_change", "eventtype"] diff --git a/repoapi/settings/common.py b/repoapi/settings/common.py index ddeb5f1..35b0f96 100644 --- a/repoapi/settings/common.py +++ b/repoapi/settings/common.py @@ -38,6 +38,7 @@ INSTALLED_APPS = [ "crispy_forms", "jsonify", "import_export", + "tracker", "hotfix", "panel", "release_dashboard", @@ -177,11 +178,6 @@ structlog.configure( ) JENKINS_TOKEN = "sipwise_jenkins_ci" -MANTIS_TOKEN = "fake_mantis_token" -MANTIS_TARGET_RELEASE = { - "id": 77, - "name": "Target Release", -} CELERY_TASK_SERIALIZER = "json" CELERY_RESULT_SERIALIZER = "json" diff --git a/repoapi/settings/prod.py b/repoapi/settings/prod.py index 005512e..be0bced 100644 --- a/repoapi/settings/prod.py +++ b/repoapi/settings/prod.py @@ -69,9 +69,8 @@ GERRIT_URL = server_config.get("gerrit", "URL") GERRIT_REST_HTTP_USER = server_config.get("gerrit", "HTTP_USER") GERRIT_REST_HTTP_PASSWD = server_config.get("gerrit", "HTTP_PASSWD") -MANTIS_URL = server_config.get("mantis", "URL") -MANTIS_TOKEN = server_config.get("mantis", "TOKEN") -MANTIS_TARGET_RELEASE["id"] = 75 # noqa +TRACKER_MANTIS_URL = server_config.get("mantis", "URL") +TRACKER_MANTIS_TOKEN = server_config.get("mantis", "TOKEN") DOCKER_REGISTRY_URL = server_config.get("server", "DOCKER_REGISTRY_URL") AUTH_LDAP_SERVER_URI = server_config.get("server", "AUTH_LDAP_SERVER_URI") @@ -136,7 +135,7 @@ STATICFILES_STORAGE = ( "django.contrib.staticfiles.storage.ManifestStaticFilesStorage" ) GITWEB_URL = "https://git.mgm.sipwise.com/gitweb/?p={}.git;a=commit;h={}" -WORKFRONT_CREDENTIALS = BASE_DIR / "/etc/jenkins_jobs/workfront.ini" +TRACKER_WORKFRONT_CREDENTIALS = BASE_DIR / "/etc/jenkins_jobs/workfront.ini" # build app BUILD_REPOS_SCRIPTS_CONFIG_DIR = Path( diff --git a/repoapi/settings/test.py b/repoapi/settings/test.py index cfc6da0..aa0a071 100644 --- a/repoapi/settings/test.py +++ b/repoapi/settings/test.py @@ -17,7 +17,7 @@ import os from pathlib import Path from .common import * # noqa -from repoapi.conf import Tracker +from tracker.conf import Tracker # pylint: disable=W0401,W0614 @@ -57,9 +57,8 @@ GERRIT_URL = "https://gerrit.local/{}" GERRIT_REST_HTTP_USER = "jenkins" GERRIT_REST_HTTP_PASSWD = "verysecrethttppasswd" GITWEB_URL = "https://git.local/gitweb/?p={}.git;a=commit;h={}" -WORKFRONT_CREDENTIALS = BASE_DIR / ".workfront.ini" +TRACKER_WORKFRONT_CREDENTIALS = BASE_DIR / ".workfront.ini" DOCKER_REGISTRY_URL = "https://localhost:5000/v2/{}" -MANTIS_URL = "https://support.local/api/rest/{}" # fake info DOCKER_REGISTRY = """ @@ -112,4 +111,4 @@ REPOAPI_ARTIFACT_JOB_REGEX = [] JBI_ALLOWED_HOSTS = ["jenkins-dev.mgm.sipwise.com"] # no tracker -REPOAPI_TRACKER = Tracker.NONE +TRACKER_PROVIDER = Tracker.NONE diff --git a/repoapi/signals.py b/repoapi/signals.py index bc6218b..811b905 100644 --- a/repoapi/signals.py +++ b/repoapi/signals.py @@ -18,7 +18,8 @@ from django.db.models.signals import post_save from django.dispatch import receiver from . import utils -from .models.wni import workfront_re_branch +from .models.wni import NoteInfo +from .models.wni import re_branch from .tasks import get_jbi_files from .tasks import jenkins_remove_project from release_dashboard.utils.build import is_ngcp_project @@ -126,48 +127,47 @@ def gerrit_repo_manage(sender, **kwargs): gerrit_repo_del(instance) -def workfront_release_target(instance, wid): +def tracker_release_target(instance, note: NoteInfo): if not is_ngcp_project(instance.projectname): logger.info( "%s not a NGCP project, skip release_target", instance.projectname ) return branch = instance.param_branch - if workfront_re_branch.search(branch): + if re_branch.search(branch): release = branch else: release = utils.get_next_release(branch) if release: - utils.workfront_set_release_target(wid, release) + note.set_target_release(release) -def workfront_note_add(instance, message, release_target=False): - WorkfrontNoteInfo = apps.get_model("repoapi", "WorkfrontNoteInfo") - wni = WorkfrontNoteInfo.objects - workfront_ids = WorkfrontNoteInfo.getIds(instance.git_commit_msg) +def tracker_note_add(instance, message, release_target=False): + model = NoteInfo.get_model() + ids = model.getIds(instance.git_commit_msg) from django.conf import settings - for wid in workfront_ids: + for id in ids: if not instance.gerrit_eventtype: - change = WorkfrontNoteInfo.getCommit(instance.git_commit_msg) + change = model.getCommit(instance.git_commit_msg) url = settings.GITWEB_URL.format(instance.projectname, change) eventtype = "git-commit" else: change = instance.gerrit_change url = settings.GERRIT_URL.format(instance.gerrit_change) eventtype = instance.gerrit_eventtype - note, created = wni.get_or_create( - workfront_id=wid, gerrit_change=change, eventtype=eventtype + note, created = NoteInfo.get_or_create( + field_id=id, gerrit_change=change, eventtype=eventtype ) if created: - if not utils.workfront_note_send(wid, "%s %s " % (message, url)): - logger.error("remove related WorkfrontNoteInfo") + if not note.send(f"{message} {url} "): + logger.error("remove related NoteInfo") note.delete() if release_target: - workfront_release_target(instance, wid) + tracker_release_target(instance, note) -def workfront_note_manage(sender, **kwargs): +def note_manager(sender, **kwargs): """ -get-code job is the first in the flow that has the proper GIT_CHANGE_SUBJECT envVar set, so git_commit_msg is fine @@ -185,7 +185,7 @@ def workfront_note_manage(sender, **kwargs): set_release_target = False else: msg = "%s.git[%s] commit created" - workfront_note_add( + tracker_note_add( instance, msg % (instance.projectname, instance.param_branch), set_release_target, diff --git a/repoapi/tasks.py b/repoapi/tasks.py index 26bd12e..3e769c2 100644 --- a/repoapi/tasks.py +++ b/repoapi/tasks.py @@ -22,13 +22,13 @@ from django.apps import apps from .celery import jbi_parse_hotfix from .celery import process_result from .conf import settings -from .conf import Tracker from .utils import is_download_artifacts from .utils import jenkins_get_artifact from .utils import jenkins_get_build from .utils import jenkins_get_console from .utils import jenkins_get_env from .utils import jenkins_remove_project_ppa +from tracker.conf import Tracker logger = structlog.get_logger(__name__) @@ -60,7 +60,7 @@ def jbi_get_artifact(jbi_id, jobname, buildnumber, artifact_info): ) path = jenkins_get_artifact(jobname, buildnumber, artifact_info) if path.name == settings.HOTFIX_ARTIFACT: - if settings.REPOAPI_TRACKER == Tracker.NONE: + if settings.TRACKER_PROVIDER == Tracker.NONE: log.info("no tracker defined, skip hotfix management") return jbi_parse_hotfix.delay(jbi_id, str(path)) diff --git a/repoapi/test/base.py b/repoapi/test/base.py index 6c2764b..927c14f 100644 --- a/repoapi/test/base.py +++ b/repoapi/test/base.py @@ -34,13 +34,13 @@ class BaseTest(TestCase): cls.path = Path(settings.JBI_BASEDIR) - def setUp(self, *args, **kwargs): + def setUp(self): RepoAPIConfig = apps.get_app_config("repoapi") RepoAPIConfig.ready() super(BaseTest, self).setUp() self.path.mkdir(parents=True, exist_ok=True) - def tearDown(self, *args, **kwargs): + def tearDown(self): if self.path.exists(): shutil.rmtree(self.path) diff --git a/repoapi/test/test_model_wni.py b/repoapi/test/test_model_wni.py new file mode 100644 index 0000000..ed83fc2 --- /dev/null +++ b/repoapi/test/test_model_wni.py @@ -0,0 +1,65 @@ +# Copyright (C) 2015-2022 The Sipwise Team - http://sipwise.com +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +from django.test import override_settings + +from repoapi.models.wni import MantisNoteInfo +from repoapi.models.wni import NoteInfo +from repoapi.models.wni import WorkfrontNoteInfo +from repoapi.test.base import BaseTest +from tracker.conf import Tracker + + +@override_settings(TRACKER_PROVIDER=Tracker.WORKFRONT) +class WorkfrontNoteTestCase(BaseTest): + def test_get_model(self): + self.assertIs(NoteInfo.get_model(), WorkfrontNoteInfo) + + def test_getID(self): + res = WorkfrontNoteInfo.getIds("jojo TT#0891 whatever") + self.assertCountEqual(res, ["0891"]) + + def test_getID_multiple(self): + res = WorkfrontNoteInfo.getIds("jojo TT#0891 whatever TT#0001") + self.assertCountEqual(res, ["0891", "0001"]) + + def test_getID_multiple_duplicate(self): + res = WorkfrontNoteInfo.getIds("jojo TT#0891 whatever TT#0001 TT#0891") + self.assertCountEqual(res, ["0891", "0001"]) + + def test_getCommit(self): + res = WorkfrontNoteInfo.getCommit("1234567 TT#67676 whatever") + self.assertEqual(res, "1234567") + + +@override_settings(TRACKER_PROVIDER=Tracker.MANTIS) +class MantisNoteTestCase(BaseTest): + def test_get_model(self): + self.assertIs(NoteInfo.get_model(), MantisNoteInfo) + + def test_getID(self): + res = MantisNoteInfo.getIds("jojo TT#0891 MT#123 whatever") + self.assertCountEqual(res, ["123"]) + + def test_getID_multiple(self): + res = MantisNoteInfo.getIds("jojo MT#0891 whatever MT#0001") + self.assertCountEqual(res, ["0891", "0001"]) + + def test_getID_multiple_duplicate(self): + res = MantisNoteInfo.getIds("jojo MT#0891 whatever MT#0001 MT#0891") + self.assertCountEqual(res, ["0891", "0001"]) + + def test_getCommit(self): + res = MantisNoteInfo.getCommit("1234567 TT#67676 MT#1234 whatever") + self.assertEqual(res, "1234567") diff --git a/repoapi/test/test_workfrontnote.py b/repoapi/test/test_workfrontnote.py index 9f39202..5cba352 100644 --- a/repoapi/test/test_workfrontnote.py +++ b/repoapi/test/test_workfrontnote.py @@ -17,30 +17,14 @@ from unittest.mock import patch from django.conf import settings from django.test import override_settings -from repoapi.conf import Tracker from repoapi.models import JenkinsBuildInfo from repoapi.models import WorkfrontNoteInfo from repoapi.test.base import BaseTest +from tracker.conf import Tracker -@override_settings(REPOAPI_TRACKER=Tracker.WORKFRONT) +@override_settings(TRACKER_PROVIDER=Tracker.WORKFRONT) class WorkfrontNoteTestCase(BaseTest): - def test_getID(self): - res = WorkfrontNoteInfo.getIds("jojo TT#0891 whatever") - self.assertCountEqual(res, ["0891"]) - - def test_getID_multiple(self): - res = WorkfrontNoteInfo.getIds("jojo TT#0891 whatever TT#0001") - self.assertCountEqual(res, ["0891", "0001"]) - - def test_getID_multiple_duplicate(self): - res = WorkfrontNoteInfo.getIds("jojo TT#0891 whatever TT#0001 TT#0891") - self.assertCountEqual(res, ["0891", "0001"]) - - def test_getCommit(self): - res = WorkfrontNoteInfo.getCommit("1234567 TT#67676 whatever") - self.assertEqual(res, "1234567") - def get_defaults(self): defaults = { "tag": "edc90cd9-37f3-4613-9748-ed05a32031c2", @@ -68,9 +52,9 @@ class WorkfrontNoteTestCase(BaseTest): del defaults["gerrit_eventtype"] return defaults - @patch("repoapi.utils.workfront_set_release_target") + @patch("tracker.utils.workfront_set_release_target") @patch("repoapi.utils.get_next_release") - @patch("repoapi.utils.workfront_note_send") + @patch("tracker.utils.workfront_note_send") def test_note_gerrit(self, wns, gnr, wsrt): param = self.get_defaults() JenkinsBuildInfo.objects.create(**param) @@ -97,9 +81,9 @@ class WorkfrontNoteTestCase(BaseTest): gnr.assert_not_called() wns.assert_called_once_with("0001", msg) - @patch("repoapi.utils.workfront_set_release_target") + @patch("tracker.utils.workfront_set_release_target") @patch("repoapi.utils.get_next_release") - @patch("repoapi.utils.workfront_note_send") + @patch("tracker.utils.workfront_note_send") def test_note_merge(self, wns, gnr, wsrt): param = self.get_defaults() JenkinsBuildInfo.objects.create(**param) @@ -157,9 +141,9 @@ class WorkfrontNoteTestCase(BaseTest): gnr.assert_called_once_with("master") wns.assert_called_with("0001", msg) - @patch("repoapi.utils.workfront_set_release_target") + @patch("tracker.utils.workfront_set_release_target") @patch("repoapi.utils.get_next_release") - @patch("repoapi.utils.workfront_note_send") + @patch("tracker.utils.workfront_note_send") def test_note_commit(self, wns, gnr, wsrt): param = self.get_non_gerrit_defaults() param["jobname"] = "kamailio-get-code" @@ -188,9 +172,9 @@ class WorkfrontNoteTestCase(BaseTest): gnr.assert_called_once_with("master") wns.assert_called_once_with("0001", msg) - @patch("repoapi.utils.workfront_set_release_target") + @patch("tracker.utils.workfront_set_release_target") @patch("repoapi.utils.get_next_release") - @patch("repoapi.utils.workfront_note_send") + @patch("tracker.utils.workfront_note_send") def test_note_commit_mrXX(self, wns, gnr, wsrt): param = self.get_non_gerrit_defaults() param["jobname"] = "kamailio-get-code" @@ -221,9 +205,9 @@ class WorkfrontNoteTestCase(BaseTest): gnr.assert_called_once_with("mr5.5") wns.assert_called_once_with("0001", msg) - @patch("repoapi.utils.workfront_set_release_target") + @patch("tracker.utils.workfront_set_release_target") @patch("repoapi.utils.get_next_release") - @patch("repoapi.utils.workfront_note_send") + @patch("tracker.utils.workfront_note_send") def test_note_commit_mrXXX(self, wns, gnr, wsrt): param = self.get_non_gerrit_defaults() param["jobname"] = "kamailio-get-code" @@ -252,9 +236,9 @@ class WorkfrontNoteTestCase(BaseTest): gnr.assert_not_called() wns.assert_called_once_with("0001", msg) - @patch("repoapi.utils.workfront_set_release_target") + @patch("tracker.utils.workfront_set_release_target") @patch("repoapi.utils.get_next_release") - @patch("repoapi.utils.workfront_note_send") + @patch("tracker.utils.workfront_note_send") def test_note_commit_next_distri(self, wns, gnr, wsrt): param = self.get_non_gerrit_defaults() param["jobname"] = "kamailio-get-code" @@ -286,9 +270,9 @@ class WorkfrontNoteTestCase(BaseTest): wsrt.assert_not_called() wns.assert_called_once_with("0001", msg) - @patch("repoapi.utils.workfront_set_release_target") + @patch("tracker.utils.workfront_set_release_target") @patch("repoapi.utils.get_next_release") - @patch("repoapi.utils.workfront_note_send") + @patch("tracker.utils.workfront_note_send") def test_note_commit_non_ngcp(self, wns, gnr, wsrt): param = self.get_non_gerrit_defaults() param["projectname"] = "fake" diff --git a/repoapi/utils.py b/repoapi/utils.py index 872eef7..f3f95f7 100644 --- a/repoapi/utils.py +++ b/repoapi/utils.py @@ -12,7 +12,6 @@ # # You should have received a copy of the GNU General Public License along # with this program. If not, see . -import json import re import subprocess from pathlib import Path @@ -29,10 +28,6 @@ JBI_CONSOLE_URL = "{}/job/{}/{}/consoleText" JBI_BUILD_URL = "{}/job/{}/{}/api/json" JBI_ARTIFACT_URL = "{}/job/{}/{}/artifact/{}" JBI_ENVVARS_URL = "{}/job/{}/{}/injectedEnvVars/api/json" -MANTIS_HEADERS = { - "Authorization": settings.MANTIS_TOKEN, - "Content-Type": "application/json", -} def executeAndReturnOutput(command, env=None): @@ -171,22 +166,6 @@ def jenkins_get_artifact(jobname, buildnumber, artifact_info): return _jenkins_get(url, base_path, artifact_info["fileName"]) -def workfront_note_send(_id, message): - command = [ - "/usr/bin/workfront-jenkins-update", - "--credfile=%s" % settings.WORKFRONT_CREDENTIALS, - "--taskid=%s" % _id, - '--message="%s"' % message, - ] - res = executeAndReturnOutput(command) - if res[0] != 0: - logger.error( - "can't post workfront notes", stdout=res[1], stderr=res[2] - ) - return False - return True - - def get_next_release(branch): command = ["/usr/bin/meta-release-helper", "--next-release", branch] res = executeAndReturnOutput(command) @@ -209,20 +188,6 @@ def get_next_release(branch): return None -def workfront_set_release_target(_id, release): - command = [ - "/usr/bin/workfront-target-task", - "--credfile=%s" % settings.WORKFRONT_CREDENTIALS, - "--taskid=%s" % _id, - "--release=%s" % release, - ] - res = executeAndReturnOutput(command) - if res[0] != 0: - logger.error("can't set release target", stdout=res[1], stderr=res[2]) - return False - return True - - def is_download_artifacts(jobname): if jobname in settings.JBI_ARTIFACT_JOBS: return True @@ -230,43 +195,3 @@ def is_download_artifacts(jobname): if re.search(check, jobname) is not None: return True return False - - -def mantis_query(method, url, payload): - logger.bind( - method=method, - url=url, - payload=payload, - ) - response = requests.request( - f"{method}", url, headers=MANTIS_HEADERS, data=payload - ) - response.raise_for_status() - return response - - -def mantis_note_send(_id, message): - url = settings.MANTIS_URL.format(f"issues/{_id}/notes") - payload = json.dumps( - {"text": f"{message}", "view_state": {"name": "private"}} - ) - mantis_query("POST", url, payload) - - -def mantis_set_release_target(_id, msg): - url = settings.MANTIS_URL.format(f"issues/{_id}") - cf = settings.MANTIS_TARGET_RELEASE - payload = json.dumps( - { - "custom_fields": [ - { - "field": { - "id": cf["id"], - "name": cf["name"], - }, - "value": f"{msg}", - }, - ] - } - ) - mantis_query("PATCH", url, payload) diff --git a/tracker/__init__.py b/tracker/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tracker/apps.py b/tracker/apps.py new file mode 100644 index 0000000..4c509ed --- /dev/null +++ b/tracker/apps.py @@ -0,0 +1,23 @@ +# Copyright (C) 2022 The Sipwise Team - http://sipwise.com +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +from django.apps import AppConfig + + +class TrackerConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "tracker" + + def ready(self): + from .conf import settings # noqa diff --git a/tracker/conf.py b/tracker/conf.py new file mode 100644 index 0000000..edf4e82 --- /dev/null +++ b/tracker/conf.py @@ -0,0 +1,46 @@ +# Copyright (C) 2022 The Sipwise Team - http://sipwise.com +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +from enum import Enum, unique +from django.conf import settings # noqa +from appconf import AppConf + + +@unique +class Tracker(Enum): + NONE = "None" + MANTIS = "Mantis" + WORKFRONT = "WorkFront" + + +class TrackerConf(AppConf): + REGEX = { + Tracker.NONE: r"#(\d+)", + Tracker.WORKFRONT: r"TT#(\d+)", + Tracker.MANTIS: r"MT#(\d+)", + } + ARTIFACT_JOB_REGEX = [ + ".*-repos$", + ] + WORKFRONT_CREDENTIALS = "fake.txt" + MANTIS_URL = "https://support.local/api/rest/{}" + MANTIS_TOKEN = "fake_mantis_token" + MANTIS_TARGET_RELEASE = { + "id": 75, + "name": "Target Release", + } + PROVIDER = Tracker.MANTIS + + class Meta: + prefix = "tracker" diff --git a/hotfix/exceptions.py b/tracker/exceptions.py similarity index 95% rename from hotfix/exceptions.py rename to tracker/exceptions.py index 680c472..8731e83 100644 --- a/hotfix/exceptions.py +++ b/tracker/exceptions.py @@ -22,3 +22,7 @@ class Error(Exception): class TrackerNotDefined(Error): pass + + +class IssueNotFound(Error): + pass diff --git a/tracker/fixtures/mantis_issue.json b/tracker/fixtures/mantis_issue.json new file mode 100644 index 0000000..14ecb27 --- /dev/null +++ b/tracker/fixtures/mantis_issue.json @@ -0,0 +1 @@ +{"issues":[{"id":36018,"summary":"TT#1597: Detect external XMPP buddies","description":"We need a mechanism to detect which XMPP buddies are external and which are not. Currently we do not allow calls on Android where the XMPP vCard does not contain a telephone number, on iOS there is nothing implemented regarding this issue.","additional_information":"WF Project Name: Sipphone App Development\nWF Project ID: 5783706800278c60f354f4dce633104f\nWF Assignments: Dominik Ridjic, Victor Seva\nWF Status: Complete\nWF Complete: 100%\nWF Entry date: 2016-07-13 11:40:48.554000\nWF Completion Date: 2016-08-09 12:38:59.180000\n","project":{"id":378,"name":"Workfront"},"category":{"id":1,"name":"None"},"reporter":{"id":613,"name":"dominik.ridjic","real_name":"Dominik Ridjic"},"handler":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"status":{"id":80,"name":"resolved","label":"resolved","color":"#d2f5b0"},"resolution":{"id":10,"name":"open","label":"open"},"view_state":{"id":10,"name":"public","label":"public"},"priority":{"id":20,"name":"P4-low","label":"P4-low"},"severity":{"id":50,"name":"minor","label":"minor"},"reproducibility":{"id":70,"name":"have not tried","label":"have not tried"},"sticky":false,"created_at":"2022-09-13T13:26:20+02:00","updated_at":"2022-09-14T03:11:06+02:00","notes":[{"id":408162,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Jenkins Deployment on 2016-07-20 13:02:58.611000 wrote (thread 578f5a62004232b2b6f973967c32e71e):\n\n \"review merged https:\/\/gerrit.mgm.sipwise.com\/7108\"","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:08:12+02:00","updated_at":"2022-09-14T03:08:12+02:00"},{"id":408178,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-20 20:46:37.585000 wrote (thread 578f5b700095a4da5a0c80c990cbeb2a):\n\n Finally works, both for domain and user search! I will later create a ticket for Alexandru and add you too","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:08:21+02:00","updated_at":"2022-09-14T03:08:21+02:00"},{"id":408187,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Jenkins Deployment on 2016-07-20 20:23:53.623000 wrote (thread 578fc1b9003d534a841b7f0b397192dd):\n\n \"commit created https:\/\/git.mgm.sipwise.com\/gitweb\/?p=prosody.git;a=commit;h=c51c1f6\"","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:08:26+02:00","updated_at":"2022-09-14T03:08:26+02:00"},{"id":408206,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-20 20:22:05.579000 wrote (thread 578f5b700095a4da5a0c80c990cbeb2a):\n\n yes, indeed. I was missing the part. Solved\n\nPlease comfirm","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:08:37+02:00","updated_at":"2022-09-14T03:08:37+02:00"},{"id":408210,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-18 11:32:47.760000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n Dominik, is this approach fine with you? Be aware of the dataform, this version supports the old schema too\n\n\n \t\t\n \t\t\t\t43991007<\/nick>\n <\/query>\n<\/iq>\n\n\t\n\t\t\n\t<\/query>\n<\/iq>","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:08:39+02:00","updated_at":"2022-09-14T03:08:39+02:00"},{"id":408212,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-20 19:20:35.757000 wrote (thread 578f5b700095a4da5a0c80c990cbeb2a):\n\n Hi Victor, it still doesn't follow the spec what you return, you are missing the jabber:x:data extension must be wrapped inside an element.\n\nPlease take a look at your example from the bugtracker:\n\n\n \n \n \n jabber:iq:search<\/value>\n <\/field>\n \n 43991007<\/value>\n <\/field>\n <\/x>\n <\/query>\n<\/iq>","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:08:40+02:00","updated_at":"2022-09-14T03:08:40+02:00"},{"id":408222,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-14 12:05:41.416000 wrote (thread 57874c07005fa3ecd674504942e0e640):\n\n I would say you could ask every time you make a connection ( waking up? ) This should be quite fast as domains are kept in memory in prosody and there will be no SQL query at all","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:08:45+02:00","updated_at":"2022-09-14T03:08:45+02:00"},{"id":408225,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-20 13:07:28.441000 wrote (thread 578f5b700095a4da5a0c80c990cbeb2a):\n\n \n\t\n\t\t\n\t\t\t\n\t\t\t\tjabber:iq:search<\/value>\n<\/field>\n\t\t\t\n\t\t\t\t*<\/value>\n<\/field>\n<\/x>\n<\/query>\n<\/iq>\n\n\n\t\n\t\t\n\t\t\tjabber:iq:search<\/value>\n\t\t<\/field>\n\t\t\n\t\t\t\n\t\t<\/reported>\n\t\t\n\t\t\t\n\t\t\t\tsearch.92.42.136.100<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tsip.sipwise.com<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tconference.sipwise.com<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\t1.2.3.4<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tconference.voip.university.com<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tlocalhost<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\t92.42.136.100<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tconference.sip.sipwise.com<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tsipwise.com<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tapogrebennyk.test<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tvoip.sipwise.local<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tsearch.apogrebennyk.test<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tconference.92.42.136.100<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tsearch.sipwise.com<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tsearch.sip.sipwise.com<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tconference.apogrebennyk.test<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tconference.1.2.3.4<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tsearch.voip.university.com<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tvoip.university.com<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tconference.voip.sipwise.local<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tsearch.voip.sipwise.local<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t\t\n\t\t\t\n\t\t\t\tsearch.1.2.3.4<\/value>\n\t\t\t<\/field>\n\t\t<\/item>\n\t<\/x>\n<\/iq>","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:08:46+02:00","updated_at":"2022-09-14T03:08:46+02:00"},{"id":408241,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-20 07:11:38.683000 wrote (thread 578e62ac00a388401f935c1a52301d9b):\n\n Ha! I now understand how I could say that I have finished implementing DataForms successfully for user search but now I get issues with the same user search.\n\nWhat happened is that somehow a bug was introduced on the server since then!\n\nLook at your comment here:\n\nhttps:\/\/bugtracker.sipwise.com\/view.php?id=18079#c148857\n\nThe request + response you give in your examples are both in DataForm format! So it worked previously the way I need it.\n\n\n jabber:iq:search<\/value>\n <\/field>\n \n <\/x>\n <\/query>\n<\/iq>\n\n\n \n \n \n jabber:iq:search<\/value>\n <\/field>\n \n 43991007<\/value>\n <\/field>\n <\/x>\n <\/query>\n<\/iq>\n\nHope this helps you","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:08:54+02:00","updated_at":"2022-09-14T03:08:54+02:00"},{"id":408259,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-18 10:59:08.114000 wrote (thread 578c9a5c006fc51dcc3f5f5c1f89199e):\n\n \n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t<\/query>\n<\/iq>\n\n \t\n\n<\/iq>\n\n\t\n\t\tUse the enclosed form to search<\/instructions>\n\t\t\n\t\t\tUser Directory Search<\/title>\n\t\t\t<instructions>Please provide the following information to search for subscribers<\/instructions>\n\t\t\t<field var='FORM_TYPE' type='hidden'>\n\t\t\t\t<value>jabber:iq:search<\/value>\n\t\t\t<\/field>\n\t\t\t<field type='text-single' label='e164 Phone number' var='e164'\/>\n\t\t\t<field type='text-single' label='domain' var='domain'\/>\n\t\t<\/x>\n\t\t<nick\/>\n\t<\/query>\n<\/iq>\n\n<iq type='set' to='search.192.168.1.43' id='search4' xml:lang='en'>\n <query xmlns='jabber:iq:search'>\n <x xmlns='jabber:x:data' type='submit'>\n <field type='hidden' var='FORM_TYPE'>\n <value>jabber:iq:search<\/value>\n <\/field>\n <field var='domain'>\n <value>*<\/value>\n <\/field>\n <\/x>\n <\/query>\n<\/iq>\n\n<iq id='search4' type='result' to='43991007@192.168.1.43\/8de87323-52fe-4296-aa36-aea8942c754c' from='search.192.168.1.43'>\n\t<query xmlns='jabber:iq:search'>\n\t\t<item domain='conference.api_test_domain.api_test_domain'\/>\n\t\t<item domain='conference.test1468801309.example.org'\/>\n\t\t<item domain='conference.192.168.1.43'\/>\n\t\t<item domain='api_test_domain.api_test_domain'\/>\n\t\t<item domain='search.test1468800773.example.org'\/>\n\t\t<item domain='localhost'\/>\n\t\t<item domain='test1468800773.example.org'\/>\n\t\t<item domain='search.voip.sipwise.local'\/>\n\t\t<item domain='search.192.168.1.43'\/>\n\t\t<item domain='test1468801309.example.org'\/>\n\t\t<item domain='192.168.1.43'\/>\n\t\t<item domain='conference.voip.sipwise.local'\/>\n\t\t<item domain='voip.sipwise.local'\/>\n\t\t<item domain='conference.test1468800773.example.org'\/>\n\t\t<item domain='conference.example.org'\/>\n\t\t<item domain='search.test1468801309.example.org'\/>\n\t\t<item domain='search.example.org'\/>\n\t\t<item domain='search.api_test_domain.api_test_domain'\/>\n\t\t<item domain='example.org'\/>\n\t<\/query>\n<\/iq>","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:03+02:00","updated_at":"2022-09-14T03:09:03+02:00"},{"id":408270,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-14 11:59:26.951000 wrote (thread 57874c07005fa3ecd674504942e0e640):\n\n fine with you?","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:09+02:00","updated_at":"2022-09-14T03:09:09+02:00"},{"id":408277,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-18 18:00:34.924000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n Hi, on what server can I test this?","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:12+02:00","updated_at":"2022-09-14T03:09:12+02:00"},{"id":408279,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-18 11:41:23.462000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n I'm just saying that this version is backwards compatible with old search style for numbers using <nick>","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:13+02:00","updated_at":"2022-09-14T03:09:13+02:00"},{"id":408280,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-14 15:43:59.369000 wrote (thread 57874c07005fa3ecd674504942e0e640):\n\n I would create a proposal with the details of query and result","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:14+02:00","updated_at":"2022-09-14T03:09:14+02:00"},{"id":408282,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-20 13:08:40.069000 wrote (thread 578f5b700095a4da5a0c80c990cbeb2a):\n\n Dominik Ridjic fix applied to sipwise.com, please test","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:15+02:00","updated_at":"2022-09-14T03:09:15+02:00"},{"id":408302,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-19 19:26:04.580000 wrote (thread 578e62ac00a388401f935c1a52301d9b):\n\n I've discovered a big issue, that both causes problems with DataForm search for users and for domains.\nHere's an example query & result for user search:\n\nquery:\n<iq to='search.sipwise.com' id='hUU0q-90' type='set'><query xmlns='jabber:iq:search'><x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>jabber:iq:search<\/value><\/field><field var='e164' type='text-single'><value>4313012039<\/value><\/field><\/x><\/query><\/iq>\n\nresult:\n<iq id='hUU0q-90' type='result' to='sparrowlabs@sipwise.com\/a62e1c69-c6c5-4a11-8e65-66b7228ee630' from='search.sipwise.com'><query xmlns='jabber:iq:search'><item jid='dboghici@sipwise.com'\/><\/query><\/iq>\n\nThe problem here is that while my query is a DataForm query, the response is not! It doesn't use DataForms.\n\nIn the specification of the search XEP you'll see that when you use DataForms in the query, the response should be a DataForm with jabber:x:data, if you don't then the result should not use DataForms.\n\nhttps:\/\/xmpp.org\/extensions\/xep-0055.html#extensibility\n\nMy problem is that my client library closely follows that specification, so when it then receives a non DataForm answer it can't parse the result (although I see that I got the response from the server)\n\nSo we would need the following change:\n\nIf I send a query with a DataForm, please return a DataForm response\nIf I send the old format, please return in the old format.\n\nDominik","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:25+02:00","updated_at":"2022-09-14T03:09:25+02:00"},{"id":408303,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Andreas Granig on 2016-07-19 17:53:17.378000 wrote (thread 578e08b20085143b0ed3179dfd6f2948):\n\n Go ahead!","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:25+02:00","updated_at":"2022-09-14T03:09:25+02:00"},{"id":408305,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-18 11:43:10.884000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n there is no old search method for domains","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:26+02:00","updated_at":"2022-09-14T03:09:26+02:00"},{"id":408306,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-14 15:32:26.186000 wrote (thread 57874c07005fa3ecd674504942e0e640):\n\n Hi Victor, fine with me, will you create some kind of API where we can query that or can this be done with command line tools in the clients? Somehow discover via querying DNS records or something like that.","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:27+02:00","updated_at":"2022-09-14T03:09:27+02:00"},{"id":408308,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-19 00:33:26.718000 wrote (thread 578d5936008c2f8dc1fbc964080f50be):\n\n Finished Implementation, waiting for a possibility to test this","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:28+02:00","updated_at":"2022-09-14T03:09:28+02:00"},{"id":408309,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Jenkins Deployment on 2016-07-18 10:32:10.871000 wrote (thread 578c940a00300d0f64f66e27c062489b):\n\n \"review created https:\/\/gerrit.mgm.sipwise.com\/7067\"","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:28+02:00","updated_at":"2022-09-14T03:09:28+02:00"},{"id":408311,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-18 11:45:54.527000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n > I'm just saying that this version is backwards compatible with old search style for numbers using <nick>\n> there is no old search method for domains\n\n???? So it works only with DataForms. I don't understand why you paste the nickname search then?","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:29+02:00","updated_at":"2022-09-14T03:09:29+02:00"},{"id":408326,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-14 12:01:55.275000 wrote (thread 57874c07005fa3ecd674504942e0e640):\n\n Hi, yes. I believe this would have to be done regularly? As server configurations can change at any moment.","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:37+02:00","updated_at":"2022-09-14T03:09:37+02:00"},{"id":408336,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-18 11:15:35.024000 wrote (thread 578c9a5c006fc51dcc3f5f5c1f89199e):\n\n <iq type='set' to='search.192.168.1.43' id='search4' lang='en'>\n\t\t<query xmlns='jabber:iq:search'>\n\t\t\t\t<x xmlns='jabber:x:data' type='submit'>\n\t\t\t\t\t\t<field type='hidden' var='FORM_TYPE'>\n\t\t\t\t\t\t\t\t<value>jabber:iq:search<\/value>\n\t\t\t<\/field>\n\t\t\t\t\t\t<field var='domain'>\n\t\t\t\t\t\t\t\t<value>192.168.1.43<\/value>\n\t\t\t<\/field>\n\t\t<\/x>\n\t<\/query>\n<\/iq>\n<iq id='search4' type='result' to='43991007@192.168.1.43\/763753ae-ce2b-4e3b-8160-22c555594d49' from='search.192.168.1.43'>\n\t<query xmlns='jabber:iq:search'>\n\t\t<item domain='192.168.1.43'\/>\n\t<\/query>\n<\/iq>\n<iq type='set' to='search.192.168.1.43' id='search4' lang='en'>\n\t\t<query xmlns='jabber:iq:search'>\n\t\t\t\t<x xmlns='jabber:x:data' type='submit'>\n\t\t\t\t\t\t<field type='hidden' var='FORM_TYPE'>\n\t\t\t\t\t\t\t\t<value>jabber:iq:search<\/value>\n\t\t\t<\/field>\n\t\t\t\t\t\t<field var='domain'>\n\t\t\t\t\t\t\t\t<value>fake.com<\/value>\n\t\t\t<\/field>\n\t\t<\/x>\n\t<\/query>\n<\/iq>\n<iq id='search4' type='result' to='43991007@192.168.1.43\/763753ae-ce2b-4e3b-8160-22c555594d49' from='search.192.168.1.43'>\n\t<query xmlns='jabber:iq:search'\/>\n<\/iq>","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:42+02:00","updated_at":"2022-09-14T03:09:42+02:00"},{"id":408346,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-18 11:51:43.628000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n Maybe is Monday and my English is getting worse. I was just pointing that this new version depends on dataforms.\nIn order to be backwards compatible with the App searching for numbers with <nick> style it supports the search of NUMBERS with <nick>.\n\nI pasted several examples of how to search the domains using dataforms and the last one is just a proof that old search of NUMBERS still works.","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:48+02:00","updated_at":"2022-09-14T03:09:48+02:00"},{"id":408348,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-13 11:41:00.462000 wrote (thread 57860cac0051b6680202c244cd7db14e):\n\n Victor Seva proposed that we do this by looking at the JID server address. If it doesn't match the XMPP server we would treat the contact as an external account. I'm not sure if this is the right solution because Andreas proposed a different one that seemed more complicated to me. (there must have been a reason for that)","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:49+02:00","updated_at":"2022-09-14T03:09:49+02:00"},{"id":408353,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Jenkins Deployment on 2016-07-20 11:15:24.059000 wrote (thread 578f412c0041ba5d7a0fa1f98c116ac1):\n\n \"review created https:\/\/gerrit.mgm.sipwise.com\/7108\"","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:52+02:00","updated_at":"2022-09-14T03:09:52+02:00"},{"id":408362,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-19 18:31:54.330000 wrote (thread 578e55fa008cfaedb613dac3b83379d4):\n\n <iq type='set' to='search.sipwise.com' id='search4' lang='en'>\n\t<query xmlns='jabber:iq:search'>\n\t\t<x xmlns='jabber:x:data' type='submit'>\n\t\t\t<field type='hidden' var='FORM_TYPE'>\n\t\t\t\t<value>jabber:iq:search<\/value>\n<\/field>\n\t\t\t<field var='domain'>\n\t\t\t\t<value>*<\/value>\n<\/field>\n<\/x>\n<\/query>\n\n<\/iq>\n<iq id='search4' type='result' to='vseva@sipwise.com\/c1b5448c-7e0d-4214-9aba-909d1ec645ed' from='search.sipwise.com'>\n\t<query xmlns='jabber:iq:search'>\n\t\t<item domain='search.92.42.136.100'\/>\n\t\t<item domain='sip.sipwise.com'\/>\n\t\t<item domain='conference.sipwise.com'\/>\n\t\t<item domain='1.2.3.4'\/>\n\t\t<item domain='conference.voip.university.com'\/>\n\t\t<item domain='localhost'\/>\n\t\t<item domain='92.42.136.100'\/>\n\t\t<item domain='conference.sip.sipwise.com'\/>\n\t\t<item domain='sipwise.com'\/>\n\t\t<item domain='apogrebennyk.test'\/>\n\t\t<item domain='voip.sipwise.local'\/>\n\t\t<item domain='search.apogrebennyk.test'\/>\n\t\t<item domain='conference.92.42.136.100'\/>\n\t\t<item domain='search.sipwise.com'\/>\n\t\t<item domain='search.sip.sipwise.com'\/>\n\t\t<item domain='conference.apogrebennyk.test'\/>\n\t\t<item domain='conference.1.2.3.4'\/>\n\t\t<item domain='search.voip.university.com'\/>\n\t\t<item domain='voip.university.com'\/>\n\t\t<item domain='conference.voip.sipwise.local'\/>\n\t\t<item domain='search.voip.sipwise.local'\/>\n\t\t<item domain='search.1.2.3.4'\/>\n\t<\/query>\n<\/iq>","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:09:56+02:00","updated_at":"2022-09-14T03:09:56+02:00"},{"id":408376,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-18 11:40:15.843000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n I mean that this version is based on dataforms and we already discuss how to support old app version that doesn't know how to deal with dataforms for searching numbers and uses <nick>","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:10:03+02:00","updated_at":"2022-09-14T03:10:03+02:00"},{"id":408377,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-18 11:37:33.806000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n It's okay with me, what is the difference with the nick search?","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:10:04+02:00","updated_at":"2022-09-14T03:10:04+02:00"},{"id":408379,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-19 13:13:32.983000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n I don't know if customers can modify something like that easily. If not it's no prob, I just mentioned it so you know that there is logic in the client that expects these field names never to change. :)","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:10:05+02:00","updated_at":"2022-09-14T03:10:05+02:00"},{"id":408392,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-19 13:11:42.250000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n > So we would have a problem if for some reason the number search field on for example the NetCologne server was named differently.\n\nAnd why it will be different? I don't get that.","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:10:12+02:00","updated_at":"2022-09-14T03:10:12+02:00"},{"id":408398,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-18 11:02:10.620000 wrote (thread 578c9a5c006fc51dcc3f5f5c1f89199e):\n\n I added a new field to our vjud module in order to be able to get the list of domains served. Magic value '*' will give you the full list","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:10:15+02:00","updated_at":"2022-09-14T03:10:15+02:00"},{"id":408400,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-18 11:54:27.562000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n Okidoki, now I get it :) So I search domains with DataForms only, yet old nick name search will still work. Currently the client is configured to always prefer DataForm search for nicknames if available anyways. Thx","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:10:16+02:00","updated_at":"2022-09-14T03:10:16+02:00"},{"id":408417,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Andreas Granig on 2016-07-14 11:53:49.522000 wrote (thread 57874c07005fa3ecd674504942e0e640):\n\n Fine with me.","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:10:24+02:00","updated_at":"2022-09-14T03:10:24+02:00"},{"id":408422,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-14 10:48:55.477000 wrote (thread 57874c07005fa3ecd674504942e0e640):\n\n I proposed that the app can ask for the list of domains that the server hosts. That is quite easy I think","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:10:27+02:00","updated_at":"2022-09-14T03:10:27+02:00"},{"id":408435,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-18 11:16:07.077000 wrote (thread 578c9a5c006fc51dcc3f5f5c1f89199e):\n\n for sure you can just ask for one domain","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:10:34+02:00","updated_at":"2022-09-14T03:10:34+02:00"},{"id":408437,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-18 11:42:36.087000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n Okay thanks, please give an example of the old search method for domains.","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:10:35+02:00","updated_at":"2022-09-14T03:10:35+02:00"},{"id":408456,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Andreas Granig on 2016-07-14 10:23:35.145000 wrote (thread 57874c07005fa3ecd674504942e0e640):\n\n If I remember correctly, I proposed a change to allow to search not only for a number, but also for a JID using jabber search.\n\nLooking at the domain is not going to work, because one server can host multiple domains, and the app might only be aware of one of them.","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:10:45+02:00","updated_at":"2022-09-14T03:10:45+02:00"},{"id":408457,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-19 00:47:07.665000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n One implementation detail: If DataForms are not available or the query does not yield any results I set the XMPP server domain as the default. So for sipwise.com all JIDs that have sipwise.com as domain will be callable by default. Please let me know if this is okay.","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:10:46+02:00","updated_at":"2022-09-14T03:10:46+02:00"},{"id":408486,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-19 13:15:08.315000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n > I don't know if customers can modify something like that easily.\nNo, this is not configurable.","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:11:02+02:00","updated_at":"2022-09-14T03:11:02+02:00"},{"id":408487,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Dominik Ridjic on 2016-07-19 13:07:11.304000 wrote (thread 578ca23f0070f761ca138a2bed930877):\n\n One more thing since now in the search DataForm two fields exist: the form field names for number search \"e164\" and for domains \"domain\" must be identical on all servers. So we would have a problem if for some reason the number search field on for example the NetCologne server was named differently.","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:11:03+02:00","updated_at":"2022-09-14T03:11:03+02:00"},{"id":408491,"reporter":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"text":"Victor Seva on 2016-07-19 13:02:10.470000 wrote (thread 578e08b20085143b0ed3179dfd6f2948):\n\n Andreas, is it OK if I install manually vjud module into sip.sipwise.com in order to allow Dominik to test this?","view_state":{"id":10,"name":"public","label":"public"},"attachments":[],"type":"note","created_at":"2022-09-14T03:11:06+02:00","updated_at":"2022-09-14T03:11:06+02:00"}],"custom_fields":[{"field":{"id":50,"name":"External Ticket Number"},"value":"57860ca00051b483245ba7e63a7d0933"},{"field":{"id":76,"name":"Portfolio Classification"},"value":""},{"field":{"id":77,"name":"Portfolio Status"},"value":""},{"field":{"id":75,"name":"Target Release"},"value":""}],"history":[{"created_at":"2022-09-13T13:26:20+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":1,"name":"issue-new"},"message":"New Issue"},{"created_at":"2022-09-13T13:26:20+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"field":{"name":"handler","label":"Assigned To"},"type":{"id":0,"name":"field-updated"},"old_value":{"id":0},"new_value":{"id":49,"name":"jenkins","real_name":"Jenkins System","email":"jenkins@sipwise.com"},"message":"Assigned To","change":" => Jenkins System"},{"created_at":"2022-09-14T03:08:12+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408162},"message":"Note Added: 0408162"},{"created_at":"2022-09-14T03:08:21+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408178},"message":"Note Added: 0408178"},{"created_at":"2022-09-14T03:08:26+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408187},"message":"Note Added: 0408187"},{"created_at":"2022-09-14T03:08:37+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408206},"message":"Note Added: 0408206"},{"created_at":"2022-09-14T03:08:39+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408210},"message":"Note Added: 0408210"},{"created_at":"2022-09-14T03:08:40+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408212},"message":"Note Added: 0408212"},{"created_at":"2022-09-14T03:08:45+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408222},"message":"Note Added: 0408222"},{"created_at":"2022-09-14T03:08:46+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408225},"message":"Note Added: 0408225"},{"created_at":"2022-09-14T03:08:54+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408241},"message":"Note Added: 0408241"},{"created_at":"2022-09-14T03:09:03+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408259},"message":"Note Added: 0408259"},{"created_at":"2022-09-14T03:09:09+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408270},"message":"Note Added: 0408270"},{"created_at":"2022-09-14T03:09:12+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408277},"message":"Note Added: 0408277"},{"created_at":"2022-09-14T03:09:13+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408279},"message":"Note Added: 0408279"},{"created_at":"2022-09-14T03:09:14+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408280},"message":"Note Added: 0408280"},{"created_at":"2022-09-14T03:09:15+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408282},"message":"Note Added: 0408282"},{"created_at":"2022-09-14T03:09:25+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408302},"message":"Note Added: 0408302"},{"created_at":"2022-09-14T03:09:25+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408303},"message":"Note Added: 0408303"},{"created_at":"2022-09-14T03:09:26+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408305},"message":"Note Added: 0408305"},{"created_at":"2022-09-14T03:09:27+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408306},"message":"Note Added: 0408306"},{"created_at":"2022-09-14T03:09:28+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408308},"message":"Note Added: 0408308"},{"created_at":"2022-09-14T03:09:28+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408309},"message":"Note Added: 0408309"},{"created_at":"2022-09-14T03:09:29+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408311},"message":"Note Added: 0408311"},{"created_at":"2022-09-14T03:09:37+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408326},"message":"Note Added: 0408326"},{"created_at":"2022-09-14T03:09:42+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408336},"message":"Note Added: 0408336"},{"created_at":"2022-09-14T03:09:48+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408346},"message":"Note Added: 0408346"},{"created_at":"2022-09-14T03:09:49+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408348},"message":"Note Added: 0408348"},{"created_at":"2022-09-14T03:09:52+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408353},"message":"Note Added: 0408353"},{"created_at":"2022-09-14T03:09:56+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408362},"message":"Note Added: 0408362"},{"created_at":"2022-09-14T03:10:03+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408376},"message":"Note Added: 0408376"},{"created_at":"2022-09-14T03:10:04+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408377},"message":"Note Added: 0408377"},{"created_at":"2022-09-14T03:10:05+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408379},"message":"Note Added: 0408379"},{"created_at":"2022-09-14T03:10:12+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408392},"message":"Note Added: 0408392"},{"created_at":"2022-09-14T03:10:15+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408398},"message":"Note Added: 0408398"},{"created_at":"2022-09-14T03:10:16+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408400},"message":"Note Added: 0408400"},{"created_at":"2022-09-14T03:10:24+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408417},"message":"Note Added: 0408417"},{"created_at":"2022-09-14T03:10:27+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408422},"message":"Note Added: 0408422"},{"created_at":"2022-09-14T03:10:34+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408435},"message":"Note Added: 0408435"},{"created_at":"2022-09-14T03:10:35+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408437},"message":"Note Added: 0408437"},{"created_at":"2022-09-14T03:10:45+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408456},"message":"Note Added: 0408456"},{"created_at":"2022-09-14T03:10:46+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408457},"message":"Note Added: 0408457"},{"created_at":"2022-09-14T03:11:02+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408486},"message":"Note Added: 0408486"},{"created_at":"2022-09-14T03:11:03+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408487},"message":"Note Added: 0408487"},{"created_at":"2022-09-14T03:11:06+02:00","user":{"id":49,"name":"jenkins","real_name":"Jenkins System"},"type":{"id":2,"name":"note-added"},"note":{"id":408491},"message":"Note Added: 0408491"}]}]} \ No newline at end of file diff --git a/tracker/migrations/__init__.py b/tracker/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tracker/models.py b/tracker/models.py new file mode 100644 index 0000000..0dcfc47 --- /dev/null +++ b/tracker/models.py @@ -0,0 +1,76 @@ +# Copyright (C) 2022 The Sipwise Team - http://sipwise.com +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +import re + +from django.db import models + +from . import utils +from .conf import Tracker +from .conf import TrackerConf + +tracker_settings = TrackerConf() + + +class TrackerInfo(models.Model): + tracker_re = re.compile(tracker_settings.REGEX[Tracker.NONE]) + + class Meta: + abstract = True + + @classmethod + def getIds(cls, change): + """ + parses text searching for tracker occurrences + returns a list of IDs + """ + if change: + res = cls.tracker_re.findall(change) + return set(res) + else: + return set() + + @property + def field_id(self): + return getattr(self, self.field_id_name) + + +class WorkfrontInfo(TrackerInfo): + workfront_id = models.CharField(max_length=50, null=False) + tracker_re = re.compile(tracker_settings.REGEX[Tracker.WORKFRONT]) + field_id_name = "workfront_id" + + class Meta: + abstract = True + + def send(self, msg: str): + return utils.workfront_note_send(self.workfront_id, msg) + + def set_target_release(self, release): + return utils.workfront_set_release_target(self.workfront_id, release) + + +class MantisInfo(TrackerInfo): + mantis_id = models.CharField(max_length=50, null=False) + tracker_re = re.compile(tracker_settings.REGEX[Tracker.MANTIS]) + field_id_name = "mantis_id" + + class Meta: + abstract = True + + def send(self, msg: str): + return utils.mantis_note_send(self.mantis_id, msg) + + def set_target_release(self, release): + return utils.mantis_set_release_target(self.mantis_id, release) diff --git a/tracker/test/__init__.py b/tracker/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tracker/test/test_utils.py b/tracker/test/test_utils.py new file mode 100644 index 0000000..8600964 --- /dev/null +++ b/tracker/test/test_utils.py @@ -0,0 +1,86 @@ +# Copyright (C) 2022 The Sipwise Team - http://sipwise.com +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +import json +from unittest.mock import patch + +from django.conf import settings +from django.test import SimpleTestCase + +from tracker import utils +from tracker.conf import TrackerConf + +FIXTURES_PATH = settings.BASE_DIR.joinpath("tracker", "fixtures") +MANTIS_ISSUE_JSON = FIXTURES_PATH.joinpath("mantis_issue.json") +MANTIS_ISSUE_ID = 36018 + +tracker_settings = TrackerConf() + + +class FakeResponse(object): + def __init__(self, filepath): + self.filepath = filepath + + def json(self): + with self.filepath.open() as f: + res = json.load(f) + return res + + +class TestFakeResponse(SimpleTestCase): + def test_json(self): + fake = FakeResponse(MANTIS_ISSUE_JSON) + res = fake.json() + self.assertTrue(len(res["issues"]), 1) + self.assertEqual(res["issues"][0]["id"], MANTIS_ISSUE_ID) + + +def set_target_release_value(issue, value): + cf = issue["custom_fields"] + for val in cf: + if val["field"]["id"] == tracker_settings.MANTIS_TARGET_RELEASE["id"]: + val["value"] = value + + +class TestUtils(SimpleTestCase): + @patch( + "tracker.utils.mantis_query", + return_value=FakeResponse(MANTIS_ISSUE_JSON), + ) + def test_mantis_get_issue_ok(self, mq): + res = utils.mantis_get_issue(MANTIS_ISSUE_ID) + self.assertEqual(res["id"], MANTIS_ISSUE_ID) + mq.assert_called_once_with( + "GET", "https://support.local/api/rest/issues/36018" + ) + + def test_mantis_get_issue_id(self): + fake = FakeResponse(MANTIS_ISSUE_JSON) + res = utils.mantis_get_issue_id(fake.json(), MANTIS_ISSUE_ID) + self.assertEqual(res["id"], MANTIS_ISSUE_ID) + + def test_mantis_get_target_releases(self): + fake = FakeResponse(MANTIS_ISSUE_JSON) + issue = utils.mantis_get_issue_id(fake.json(), MANTIS_ISSUE_ID) + set_target_release_value(issue, "mr10.1") + res = utils.mantis_get_target_releases(issue) + self.assertListEqual(res, ["mr10.1"]) + + set_target_release_value(issue, "mr10.1, mr8.5.1") + res = utils.mantis_get_target_releases(issue) + self.assertListEqual(res, ["mr8.5.1", "mr10.1"]) + + set_target_release_value(issue, "mr10.1, mr8.5.1,,") + res = utils.mantis_get_target_releases(issue) + self.assertListEqual(res, ["mr8.5.1", "mr10.1"]) diff --git a/tracker/utils.py b/tracker/utils.py new file mode 100644 index 0000000..a5dfe0f --- /dev/null +++ b/tracker/utils.py @@ -0,0 +1,131 @@ +# Copyright (C) 2022 The Sipwise Team - http://sipwise.com +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +import json + +import requests +import structlog +from natsort import humansorted + +from .conf import TrackerConf +from .exceptions import IssueNotFound +from repoapi.utils import executeAndReturnOutput + +tracker_settings = TrackerConf() +logger = structlog.get_logger(__name__) + +MANTIS_HEADERS = { + "Authorization": tracker_settings.MANTIS_TOKEN, + "Content-Type": "application/json", +} + + +def workfront_note_send(_id, message): + command = [ + "/usr/bin/workfront-jenkins-update", + "--credfile=%s" % tracker_settings.WORKFRONT_CREDENTIALS, + "--taskid=%s" % _id, + '--message="%s"' % message, + ] + res = executeAndReturnOutput(command) + if res[0] != 0: + logger.error( + "can't post workfront notes", stdout=res[1], stderr=res[2] + ) + return False + return True + + +def workfront_set_release_target(_id, release): + command = [ + "/usr/bin/workfront-target-task", + "--credfile=%s" % tracker_settings.WORKFRONT_CREDENTIALS, + "--taskid=%s" % _id, + "--release=%s" % release, + ] + res = executeAndReturnOutput(command) + if res[0] != 0: + logger.error("can't set release target", stdout=res[1], stderr=res[2]) + return False + return True + + +def mantis_query(method, url, payload=None) -> requests.Response: + logger.bind( + method=method, + url=url, + payload=payload, + ) + response = requests.request( + f"{method}", url, headers=MANTIS_HEADERS, data=payload + ) + response.raise_for_status() + return response + + +def mantis_get_issue_id(res, _id: int): + for issue in res["issues"]: + if issue["id"] == _id: + return issue + + +def mantis_get_issue(_id: int): + url = tracker_settings.MANTIS_URL.format(f"issues/{_id}") + response = mantis_query("GET", url) + res = mantis_get_issue_id(response.json(), _id) + if res: + return res + raise IssueNotFound(f"{_id} Not found in response") + + +def mantis_note_send(_id: int, message: str) -> requests.Response: + url = tracker_settings.MANTIS_URL.format(f"issues/{_id}/notes") + payload = json.dumps( + {"text": f"{message}", "view_state": {"name": "private"}} + ) + return mantis_query("POST", url, payload) + + +def mantis_get_target_releases(issue) -> list: + cf = issue["custom_fields"] + res = set() + for val in cf: + if val["field"]["id"] == tracker_settings.MANTIS_TARGET_RELEASE["id"]: + for word in val["value"].split(","): + word = word.strip() + if word: + res.add(word) + break + return humansorted(res) + + +def mantis_set_release_target(_id: int, release: str) -> requests.Response: + issue = mantis_get_issue(_id) + releases = mantis_get_target_releases(issue) + url = tracker_settings.MANTIS_URL.format(f"issues/{_id}") + cf = tracker_settings.MANTIS_TARGET_RELEASE + payload = json.dumps( + { + "custom_fields": [ + { + "field": { + "id": cf["id"], + "name": cf["name"], + }, + "value": f"{releases}", + }, + ] + } + ) + return mantis_query("PATCH", url, payload)