diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 96f7585688..585b4ed43a 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -463,9 +463,10 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin): success_notifiers = list(base_notifiers.filter(unifiedjobtemplate_notifiers_for_success__in=[self, self.project])) any_notifiers = list(base_notifiers.filter(unifiedjobtemplate_notifiers_for_any__in=[self, self.project])) # Get Organization Notifiers - error_notifiers = set(error_notifiers + list(base_notifiers.filter(organization_notifiers_for_errors=self.project.organization))) - success_notifiers = set(success_notifiers + list(base_notifiers.filter(organization_notifiers_for_success=self.project.organization))) - any_notifiers = set(any_notifiers + list(base_notifiers.filter(organization_notifiers_for_any=self.project.organization))) + if self.project is not None and self.project.organization is not None: + error_notifiers = set(error_notifiers + list(base_notifiers.filter(organization_notifiers_for_errors=self.project.organization))) + success_notifiers = set(success_notifiers + list(base_notifiers.filter(organization_notifiers_for_success=self.project.organization))) + any_notifiers = set(any_notifiers + list(base_notifiers.filter(organization_notifiers_for_any=self.project.organization))) return dict(error=list(error_notifiers), success=list(success_notifiers), any=list(any_notifiers)) class Job(UnifiedJob, JobOptions): @@ -533,14 +534,13 @@ class Job(UnifiedJob, JobOptions): def is_blocked_by(self, obj): from awx.main.models import InventoryUpdate, ProjectUpdate if type(obj) == Job: - if obj.job_template is not None and obj.job_template == self.job_template: - if obj.launch_type == 'callback' and self.launch_type == 'callback': - if obj.limit != self.limit: - # NOTE: This is overriden by api/views.py.JobTemplateCallback.post() check - # which limits job runs on a JT to one per host in a callback scenario - # I'm leaving this here in case we change that + if obj.job_template is not None and obj.inventory is not None: + if obj.job_template == self.job_template and \ + obj.inventory == self.inventory: + if obj.launch_type == 'callback' and self.launch_type == 'callback' and \ + obj.limit != self.limit: return False - return True + return True return False if type(obj) == InventoryUpdate: if self.inventory == obj.inventory_source.inventory: diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py index cdc4d17320..12357fca2e 100644 --- a/awx/main/models/projects.py +++ b/awx/main/models/projects.py @@ -362,9 +362,10 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin): success_notifiers = list(base_notifiers.filter(unifiedjobtemplate_notifiers_for_success=self)) any_notifiers = list(base_notifiers.filter(unifiedjobtemplate_notifiers_for_any=self)) # Get Organization Notifiers - error_notifiers = set(error_notifiers + list(base_notifiers.filter(organization_notifiers_for_errors=self.organization))) - success_notifiers = set(success_notifiers + list(base_notifiers.filter(organization_notifiers_for_success=self.organization))) - any_notifiers = set(any_notifiers + list(base_notifiers.filter(organization_notifiers_for_any=self.organization))) + if self.organization is not None: + error_notifiers = set(error_notifiers + list(base_notifiers.filter(organization_notifiers_for_errors=self.organization))) + success_notifiers = set(success_notifiers + list(base_notifiers.filter(organization_notifiers_for_success=self.organization))) + any_notifiers = set(any_notifiers + list(base_notifiers.filter(organization_notifiers_for_any=self.organization))) return dict(error=list(error_notifiers), success=list(success_notifiers), any=list(any_notifiers)) def get_absolute_url(self): diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py index e187d8cd03..97e31c3269 100644 --- a/awx/main/tests/functional/conftest.py +++ b/awx/main/tests/functional/conftest.py @@ -28,6 +28,7 @@ from awx.main.models.credential import Credential from awx.main.models.jobs import JobTemplate from awx.main.models.inventory import ( Group, + Inventory, ) from awx.main.models.organization import ( Organization, @@ -175,6 +176,16 @@ def machine_credential(): def inventory(organization): return organization.inventories.create(name="test-inv") +@pytest.fixture +def inventory_factory(organization): + def factory(name, org=organization): + try: + inv = Inventory.objects.get(name=name, organization=org) + except Inventory.DoesNotExist: + inv = Inventory.objects.create(name=name, organization=org) + return inv + return factory + @pytest.fixture def label(organization): return organization.labels.create(name="test-label", description="test-label-desc") diff --git a/awx/main/tests/functional/test_jobs.py b/awx/main/tests/functional/test_jobs.py new file mode 100644 index 0000000000..aefb4e8bb7 --- /dev/null +++ b/awx/main/tests/functional/test_jobs.py @@ -0,0 +1,24 @@ +from awx.main.models import Job + +import pytest + +@pytest.mark.django_db +def test_job_blocking(get, post, job_template, inventory, inventory_factory): + j1 = Job.objects.create(job_template=job_template, + inventory=inventory) + j2 = Job.objects.create(job_template=job_template, + inventory=inventory) + assert j1.is_blocked_by(j2) + j2.inventory = inventory_factory(name='test-different-inventory') + assert not j1.is_blocked_by(j2) + j_callback_1 = Job.objects.create(job_template=job_template, + inventory=inventory, + launch_type='callback', + limit='a') + j_callback_2 = Job.objects.create(job_template=job_template, + inventory=inventory, + launch_type='callback', + limit='a') + assert j_callback_1.is_blocked_by(j_callback_2) + j_callback_2.limit = 'b' + assert not j_callback_1.is_blocked_by(j_callback_2) diff --git a/awx/ui/client/src/system-tracking/fact-data-table/fact-data-table.block.less b/awx/ui/client/src/system-tracking/fact-data-table/fact-data-table.block.less index 67bfcf42fa..f2a2c74995 100644 --- a/awx/ui/client/src/system-tracking/fact-data-table/fact-data-table.block.less +++ b/awx/ui/client/src/system-tracking/fact-data-table/fact-data-table.block.less @@ -49,7 +49,6 @@ padding: 1em 12px; color: @default-as-detail-txt; font-size: 14px; - text-transform: uppercase; } &-column { diff --git a/awx/ui/client/src/system-tracking/fact-module-pickers.block.less b/awx/ui/client/src/system-tracking/fact-module-pickers.block.less index b494456981..f4d6d35022 100644 --- a/awx/ui/client/src/system-tracking/fact-module-pickers.block.less +++ b/awx/ui/client/src/system-tracking/fact-module-pickers.block.less @@ -1,31 +1,3 @@ -// /** @define FactModulePickers */ -// -// .FactModulePickers { -// //width: 100%; -// display: flex; -// margin-bottom: 15px; -// -// &-dateContainer { -// flex: 1 0 auto; -// display: flex; -// flex-direction: column -// } -// -// &-dateContainer--left { -// margin-right: 7px; -// } -// -// &-dateContainer--right { -// margin-left: 7px; -// } -// -// &-label { -// flex: 1 0 auto; -// font-weight: 700; -// padding-bottom: 5px; -// } -// } - @import "../../shared/branding/colors.default.less"; .FactModulePickers-label { @@ -36,3 +8,11 @@ color: @default-interface-txt; line-height: 17px; } + +.FactModulePickers-warning { + float: right; + clear: both; + font-size: 12px; + width: 75%; + text-align: right; +} diff --git a/awx/ui/client/src/system-tracking/system-tracking.route.js b/awx/ui/client/src/system-tracking/system-tracking.route.js index 0149587769..4fcd8e1b25 100644 --- a/awx/ui/client/src/system-tracking/system-tracking.route.js +++ b/awx/ui/client/src/system-tracking/system-tracking.route.js @@ -13,6 +13,9 @@ export default { templateUrl: templateUrl('system-tracking/system-tracking'), params: {hosts: null, inventory: null}, reloadOnSearch: false, + ncyBreadcrumb: { + label: "SYSTEM TRACKING" + }, resolve: { moduleOptions: [ 'getModuleOptions',