diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 0493fbb1f6..5c3edddb52 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -4684,8 +4684,14 @@ class SchedulePreviewSerializer(BaseSerializer): class ScheduleSerializer(LaunchConfigurationBaseSerializer, SchedulePreviewSerializer): show_capabilities = ['edit', 'delete'] - timezone = serializers.SerializerMethodField() - until = serializers.SerializerMethodField() + timezone = serializers.SerializerMethodField( + help_text=_( + 'The timezone this schedule runs in. This field is extracted from the RRULE. If the timezone in the RRULE is a link to another timezone, the link will be reflected in this field.' + ), + ) + until = serializers.SerializerMethodField( + help_text=_('The date this schedule will end. This field is computed from the RRULE. If the schedule does not end an emptry string will be returned'), + ) class Meta: model = Schedule diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index f39c627c48..54dcf7034b 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -578,8 +578,7 @@ class ScheduleZoneInfo(APIView): swagger_topic = 'System Configuration' def get(self, request): - zones = [{'name': zone} for zone in models.Schedule.get_zoneinfo()] - return Response(zones) + return Response({'zones': models.Schedule.get_zoneinfo(), 'links': models.Schedule.get_zoneinfo_links()}) class LaunchConfigCredentialsBase(SubListAttachDetachAPIView): diff --git a/awx/main/models/schedules.py b/awx/main/models/schedules.py index 8f9caec131..29d43ec98d 100644 --- a/awx/main/models/schedules.py +++ b/awx/main/models/schedules.py @@ -85,9 +85,18 @@ class Schedule(PrimordialModel, LaunchTimeConfig): next_run = models.DateTimeField(null=True, default=None, editable=False, help_text=_("The next time that the scheduled action will run.")) @classmethod - def get_zoneinfo(self): + def get_zoneinfo(cls): return sorted(get_zonefile_instance().zones) + @classmethod + def get_zoneinfo_links(cls): + return_val = {} + zone_instance = get_zonefile_instance() + for zone_name in zone_instance.zones: + if str(zone_name) != str(zone_instance.zones[zone_name]._filename): + return_val[zone_name] = zone_instance.zones[zone_name]._filename + return return_val + @property def timezone(self): utc = tzutc() diff --git a/awx/main/tests/functional/api/test_schedules.py b/awx/main/tests/functional/api/test_schedules.py index e38fd09647..9bd85b3c0e 100644 --- a/awx/main/tests/functional/api/test_schedules.py +++ b/awx/main/tests/functional/api/test_schedules.py @@ -500,7 +500,7 @@ def test_complex_schedule(post, admin_user, rrule, expected_result): def test_zoneinfo(get, admin_user): url = reverse('api:schedule_zoneinfo') r = get(url, admin_user, expect=200) - assert {'name': 'America/New_York'} in r.data + assert 'America/New_York' in r.data['zones'] @pytest.mark.django_db diff --git a/awx/ui/src/components/Schedule/shared/ScheduleForm.js b/awx/ui/src/components/Schedule/shared/ScheduleForm.js index 48da8e7664..86ad24f08f 100644 --- a/awx/ui/src/components/Schedule/shared/ScheduleForm.js +++ b/awx/ui/src/components/Schedule/shared/ScheduleForm.js @@ -89,7 +89,7 @@ const generateRunOnTheDay = (days = []) => { return null; }; -function ScheduleFormFields({ hasDaysToKeepField, zoneOptions }) { +function ScheduleFormFields({ hasDaysToKeepField, zoneOptions, zoneLinks }) { const [timezone, timezoneMeta] = useField({ name: 'timezone', validate: required(t`Select a value for this field`), @@ -100,6 +100,24 @@ function ScheduleFormFields({ hasDaysToKeepField, zoneOptions }) { }); const [{ name: dateFieldName }] = useField('startDate'); const [{ name: timeFieldName }] = useField('startTime'); + const [timezoneMessage, setTimezoneMessage] = useState(''); + const warnLinkedTZ = (event, selectedValue) => { + if (zoneLinks[selectedValue]) { + setTimezoneMessage( + `Warning: ${selectedValue} is a link to ${zoneLinks[selectedValue]} and will be saved as that.` + ); + } else { + setTimezoneMessage(''); + } + timezone.onChange(event, selectedValue); + }; + + let timezoneValidatedStatus = 'default'; + if (timezoneMeta.touched && timezoneMeta.error) { + timezoneValidatedStatus = 'error'; + } else if (timezoneMessage) { + timezoneValidatedStatus = 'warning'; + } return ( <> { const { data } = await SchedulesAPI.readZoneInfo(); @@ -225,19 +243,21 @@ function ScheduleForm({ creds = results; } - const zones = data.map((zone) => ({ - value: zone.name, - key: zone.name, - label: zone.name, + const zones = (data.zones || []).map((zone) => ({ + value: zone, + key: zone, + label: zone, })); return { zoneOptions: zones, + zoneLinks: data.links, credentials: creds || [], }; }, [schedule]), { zonesOptions: [], + zoneLinks: {}, credentials: [], isLoading: true, } @@ -630,6 +650,7 @@ function ScheduleForm({ {isWizardOpen && (