Merge pull request #776 from grafana/dev

Release new helm chart version 1.0.9 (v1.0.50)
This commit is contained in:
Michael Derynck 2022-11-05 01:23:10 -06:00 committed by GitHub
commit 144abaabc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1358 additions and 767 deletions

View file

@ -1,5 +1,9 @@
# Change Log
## v1.0.51 (2022-11-05)
- Bug Fixes
## v1.0.50 (2022-11-03)
- Updates to documentation

View file

@ -11,7 +11,7 @@ PYTEST = $(ENV)/bin/pytest
DOCKER_FILE ?= docker-compose-developer.yml
define setup_engine_env
export `grep -v '^#' .env | xargs -0` && cd engine
export `grep -v '^#' .env.dev | xargs -0` && cd engine
endef
$(ENV):

View file

@ -29,6 +29,4 @@ Grafana OnCall is an open source incident response management tool built to help
- **Massive scalability:** Grafana OnCall is equipped with a full API and Terraform capabilities. Ready for GitOps and large organization configuration.
> **Note:** You can use [Grafana Cloud](https://grafana.com/products/cloud/?plcmt=nav-products-cta1&cta=cloud) to avoid installing, maintaining, and scaling your own instance of Grafana OnCall. The free forever plan includes 30 Grafana OnCall notification. [Create an account to get started](https://grafana.com/auth/sign-up/create-user?pg=oncall&plcmt=hero-btn-1).
{{< section >}}

View file

@ -18,3 +18,6 @@ Once Grafana OnCall receives an alert, the following occurs, based on the alert
- Default or customized alert templates are applied to deliver the most useful alert fields with the most valuable information, in a readable format.
- Alerts are grouped based on your alert grouping configurations, combining similar or related alerts to reduce alert noise.
- Alerts automatically resolve if an alert from the monitoring system matches the resolve condition for that alert.
{{< section >}}

View file

@ -26,10 +26,7 @@ These procedures introduce you to initial Grafana OnCall configuration steps, in
## Before you begin
Grafana OnCall is available for Grafana Cloud as well as Grafana open source users. You must have a Grafana Cloud account or [Open Source Grafana OnCall]({{< relref "../open-source" >}})
For more information, see [Grafana Pricing](https://grafana.com/pricing/) for details.
Grafana OnCall is available for Grafana Cloud as well as Grafana open source users. You must have a Grafana Cloud account or use [Open Source Grafana OnCall]({{< relref "../open-source" >}})
## Install Open Source Grafana OnCall

View file

@ -1,4 +1,4 @@
FROM python:3.9-alpine
FROM python:3.9-alpine3.16
RUN apk add bash python3-dev build-base linux-headers pcre-dev mariadb-connector-c-dev openssl-dev libffi-dev git
RUN pip install uwsgi

View file

@ -14,7 +14,6 @@ from django.dispatch import receiver
from django.utils import timezone
from django.utils.crypto import get_random_string
from emoji import emojize
from jinja2 import Template
from apps.alerts.grafana_alerting_sync_manager.grafana_alerting_sync import GrafanaAlertingSyncManager
from apps.alerts.integration_options_mixin import IntegrationOptionsMixin
@ -29,6 +28,7 @@ from apps.slack.utils import post_message_to_channel
from common.api_helpers.utils import create_engine_url
from common.exceptions import TeamCanNotBeChangedError, UnableToSendDemoAlert
from common.insight_log import EntityEvent, write_resource_insight_log
from common.jinja_templater import jinja_template_env
from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length
logger = logging.getLogger(__name__)
@ -360,7 +360,7 @@ class AlertReceiveChannel(IntegrationOptionsMixin, MaintainableObject):
def description(self):
if self.integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING:
contact_points = self.contact_points.all()
rendered_description = Template(self.config.description).render(
rendered_description = jinja_template_env.from_string(self.config.description).render(
is_finished_alerting_setup=self.is_finished_alerting_setup,
grafana_alerting_entities=[
{

View file

@ -7,9 +7,9 @@ from django.core.validators import MinLengthValidator
from django.db import models
from django.db.models import F
from django.utils import timezone
from jinja2 import Template
from requests.auth import HTTPBasicAuth
from common.jinja_templater import jinja_template_env
from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length
logger = logging.getLogger(__name__)
@ -103,7 +103,7 @@ class CustomButton(models.Model):
if self.forward_whole_payload:
post_kwargs["json"] = alert.raw_request_data
elif self.data:
rendered_data = Template(self.data).render(
rendered_data = jinja_template_env.from_string(self.data).render(
{
"alert_payload": self._escape_alert_payload(alert.raw_request_data),
"alert_group_id": alert.group.public_primary_key,

View file

@ -2,13 +2,14 @@ import json
from collections import defaultdict
from django.core.validators import URLValidator, ValidationError
from jinja2 import Template, TemplateError
from jinja2 import TemplateError
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator
from apps.alerts.models import CustomButton
from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField
from common.api_helpers.utils import CurrentOrganizationDefault, CurrentTeamDefault
from common.jinja_templater import jinja_template_env
class CustomButtonSerializer(serializers.ModelSerializer):
@ -52,7 +53,7 @@ class CustomButtonSerializer(serializers.ModelSerializer):
return None
try:
template = Template(data)
template = jinja_template_env.from_string(data)
except TemplateError:
raise serializers.ValidationError("Data has incorrect template")

View file

@ -114,8 +114,12 @@ class OnCallShiftSerializer(EagerLoadingMixin, serializers.ModelSerializer):
raise serializers.ValidationError(
{"frequency": ["Cannot set 'frequency' for shifts with type 'override'"]}
)
if frequency != CustomOnCallShift.FREQUENCY_WEEKLY and by_day:
if frequency not in (CustomOnCallShift.FREQUENCY_WEEKLY, CustomOnCallShift.FREQUENCY_DAILY) and by_day:
raise serializers.ValidationError({"by_day": ["Cannot set days value for this frequency type"]})
if frequency == CustomOnCallShift.FREQUENCY_DAILY and by_day and interval > len(by_day):
raise serializers.ValidationError(
{"interval": ["Interval must be less than or equal to the number of selected days"]}
)
def _validate_rotation_start(self, shift_start, rotation_start):
if rotation_start < shift_start:

View file

@ -739,7 +739,7 @@ def test_create_on_call_shift_invalid_data_by_day(on_call_shift_internal_api_set
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data["by_day"][0] == "Cannot set days value for non-recurrent shifts"
# by_day with non-weekly frequency
# by_day with non-weekly/non-daily frequency
data = {
"title": "Test Shift 2",
"type": CustomOnCallShift.TYPE_ROLLING_USERS_EVENT,
@ -749,7 +749,7 @@ def test_create_on_call_shift_invalid_data_by_day(on_call_shift_internal_api_set
"shift_end": (start_date + timezone.timedelta(hours=2)).strftime("%Y-%m-%dT%H:%M:%SZ"),
"rotation_start": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"until": None,
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
"frequency": CustomOnCallShift.FREQUENCY_MONTHLY,
"interval": None,
"by_day": [CustomOnCallShift.ICAL_WEEKDAY_MAP[CustomOnCallShift.MONDAY]],
"rolling_users": [[user1.public_primary_key]],
@ -789,6 +789,27 @@ def test_create_on_call_shift_invalid_data_interval(on_call_shift_internal_api_s
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data["interval"][0] == "Cannot set interval for non-recurrent shifts"
# by_day, daily, interval > len(by_day)
data = {
"title": "Test Shift 2",
"type": CustomOnCallShift.TYPE_ROLLING_USERS_EVENT,
"schedule": schedule.public_primary_key,
"priority_level": 0,
"shift_start": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"shift_end": (start_date + timezone.timedelta(hours=2)).strftime("%Y-%m-%dT%H:%M:%SZ"),
"rotation_start": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"until": None,
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
"interval": 2,
"by_day": [CustomOnCallShift.ICAL_WEEKDAY_MAP[CustomOnCallShift.MONDAY]],
"rolling_users": [[user1.public_primary_key]],
}
response = client.post(url, data, format="json", **make_user_auth_headers(user1, token))
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data["interval"][0] == "Interval must be less than or equal to the number of selected days"
@pytest.mark.django_db
def test_create_on_call_shift_invalid_data_shift_end(on_call_shift_internal_api_setup, make_user_auth_headers):

View file

@ -1,3 +1,5 @@
import datetime
import pytz
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Count, OuterRef, Subquery
@ -295,7 +297,11 @@ class ScheduleView(
users = {u: None for u in schedule.related_users()}
for e in events:
user = e["users"][0]["pk"] if e["users"] else None
if user is not None and users.get(user) is None and e["end"] > now:
event_end = e["end"]
if not isinstance(event_end, datetime.datetime):
# all day events end is a date, make it a datetime for comparison
event_end = datetime.datetime.combine(event_end, datetime.datetime.min.time(), tzinfo=pytz.UTC)
if user is not None and users.get(user) is None and event_end > now:
users[user] = e
result = {"users": users}

View file

@ -2,13 +2,14 @@ import json
from collections import defaultdict
from django.core.validators import URLValidator, ValidationError
from jinja2 import Template, TemplateError
from jinja2 import TemplateError
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator
from apps.alerts.models import CustomButton
from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField
from common.api_helpers.utils import CurrentOrganizationDefault
from common.jinja_templater import jinja_template_env
class ActionCreateSerializer(serializers.ModelSerializer):
@ -56,7 +57,7 @@ class ActionCreateSerializer(serializers.ModelSerializer):
return None
try:
template = Template(data)
template = jinja_template_env.from_string(data)
except TemplateError:
raise serializers.ValidationError("Data has incorrect template")

View file

@ -129,6 +129,7 @@ class CustomOnCallShiftSerializer(EagerLoadingMixin, serializers.ModelSerializer
self._validate_frequency_daily(
validated_data["type"],
validated_data.get("frequency"),
validated_data.get("interval"),
validated_data.get("by_day"),
validated_data.get("by_monthday"),
)
@ -201,14 +202,16 @@ class CustomOnCallShiftSerializer(EagerLoadingMixin, serializers.ModelSerializer
elif frequency == CustomOnCallShift.FREQUENCY_WEEKLY and week_start is None:
raise BadRequest(detail="Field 'week_start' is required for frequency type 'weekly'")
def _validate_frequency_daily(self, event_type, frequency, by_day, by_monthday):
def _validate_frequency_daily(self, event_type, frequency, interval, by_day, by_monthday):
if event_type == CustomOnCallShift.TYPE_ROLLING_USERS_EVENT:
if frequency == CustomOnCallShift.FREQUENCY_DAILY:
if by_day or by_monthday:
if by_monthday:
raise BadRequest(
detail="Day limits are temporarily disabled for on-call shifts with type 'rolling_users' "
"and frequency 'daily'"
)
if by_day and interval > len(by_day):
raise BadRequest(detail="Interval must be less than or equal to the number of selected days")
def _validate_start_rotation_from_user_index(self, type, index):
if type == CustomOnCallShift.TYPE_ROLLING_USERS_EVENT and index is None:
@ -354,9 +357,10 @@ class CustomOnCallShiftUpdateSerializer(CustomOnCallShiftSerializer):
if frequency != instance.frequency:
self._validate_frequency_and_week_start(event_type, frequency, week_start)
interval = validated_data.get("interval", instance.interval)
by_day = validated_data.get("by_day", instance.by_day)
by_monthday = validated_data.get("by_monthday", instance.by_monthday)
self._validate_frequency_daily(event_type, frequency, by_day, by_monthday)
self._validate_frequency_daily(event_type, frequency, interval, by_day, by_monthday)
if start_rotation_from_user_index != instance.start_rotation_from_user_index:
self._validate_start_rotation_from_user_index(event_type, start_rotation_from_user_index)

View file

@ -40,6 +40,11 @@ invalid_field_data_8 = {
"until": "not-a-date",
}
invalid_field_data_9 = {
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
"interval": 5,
}
@pytest.mark.django_db
def test_get_on_call_shift(make_organization_and_user_with_token, make_on_call_shift, make_schedule):
@ -284,6 +289,7 @@ def test_update_on_call_shift(make_organization_and_user_with_token, make_on_cal
invalid_field_data_6,
invalid_field_data_7,
invalid_field_data_8,
invalid_field_data_9,
],
)
def test_update_on_call_shift_invalid_field(make_organization_and_user_with_token, make_on_call_shift, data_to_update):

View file

@ -106,6 +106,8 @@ class AmixrRecurringIcalEventsAdapter(IcalService):
def filter_extra_days(event):
event_start, event_end = self.get_start_and_end_with_respect_to_event_type(event)
if event_start > event_end:
return False
return time_span_contains_event(start_date, end_date, event_start, event_end)
return list(filter(filter_extra_days, events))

View file

@ -1,3 +1,5 @@
import copy
import itertools
import logging
import random
import string
@ -274,6 +276,70 @@ class CustomOnCallShift(models.Model):
return is_finished
def _daily_by_day_to_ical(self, time_zone, start, users_queue):
"""Create ical weekly shifts to distribute user groups combining daily + by_day.
e.g.
by_day: [WED, FRI]
users_queue: [user_group_1, user_group_2, user_group_3]
will result in the following ical shift rules:
user_group_1, weekly WED interval 3
user_group_2, weekly FRI interval 3
user_group_3, weekly WED interval 3
user_group_1, weekly FRI interval 3
user_group_2, weekly WED interval 3
user_group_3, weekly FRI interval 3
"""
result = ""
# keep tracking of (users, day) combinations, and starting dates for each
combinations = []
starting_dates = []
# we may need to iterate several times over users until we get a seen combination
# use the group index as reference since user groups could repeat in the queue
cycle_user_groups = itertools.cycle(range(len(users_queue)))
orig_start = last_start = start
all_rotations_checked = False
# we need to go through each individual day
day_by_day_rrule = copy.deepcopy(self.event_ical_rules)
day_by_day_rrule["interval"] = 1
for user_group_id in cycle_user_groups:
for i in range(self.interval):
if not start: # means that rotation ends before next event starts
all_rotations_checked = True
break
last_start = start
day = CustomOnCallShift.ICAL_WEEKDAY_MAP[start.weekday()]
if (user_group_id, day, i) in combinations:
all_rotations_checked = True
break
starting_dates.append(start)
combinations.append((user_group_id, day, i))
# get next event date following the original rule
event_ical = self.generate_ical(start, 1, None, 1, time_zone, custom_rrule=day_by_day_rrule)
start = self.get_rotation_date(event_ical, get_next_date=True, interval=1)
if all_rotations_checked:
break
# number of weeks used to cover all combinations
week_interval = ((last_start - orig_start).days // 7) or 1
counter = 1
for ((user_group_id, day, _), start) in zip(combinations, starting_dates):
users = users_queue[user_group_id]
for user_counter, user in enumerate(users, start=1):
# setup weekly events, for each user group/day combinations,
# setting the right interval and the corresponding day
custom_rrule = copy.deepcopy(self.event_ical_rules)
custom_rrule["freq"] = ["WEEKLY"]
custom_rrule["interval"] = [week_interval]
custom_rrule["byday"] = [day]
custom_event_ical = self.generate_ical(
start, user_counter, user, counter, time_zone, custom_rrule=custom_rrule
)
result += custom_event_ical
counter += 1
return result
def convert_to_ical(self, time_zone="UTC", allow_empty_users=False):
result = ""
# use shift time_zone if it exists, otherwise use schedule or default time_zone
@ -299,6 +365,10 @@ class CustomOnCallShift(models.Model):
else:
start = self.get_rotation_date(event_ical)
if self.frequency == CustomOnCallShift.FREQUENCY_DAILY and self.by_day:
result = self._daily_by_day_to_ical(time_zone, start, users_queue)
all_rotation_checked = True
while not all_rotation_checked:
for counter, users in enumerate(users_queue, start=1):
if not start: # means that rotation ends before next event starts
@ -325,7 +395,7 @@ class CustomOnCallShift(models.Model):
result += self.generate_ical(self.start, user_counter, user, time_zone=time_zone)
return result
def generate_ical(self, start, user_counter=0, user=None, counter=1, time_zone="UTC"):
def generate_ical(self, start, user_counter=0, user=None, counter=1, time_zone="UTC", custom_rrule=None):
event = Event()
event["uid"] = f"oncall-{self.uuid}-PK{self.public_primary_key}-U{user_counter}-E{counter}-S{self.source}"
if user:
@ -333,7 +403,9 @@ class CustomOnCallShift(models.Model):
event.add("dtstart", self.convert_dt_to_schedule_timezone(start, time_zone))
event.add("dtend", self.convert_dt_to_schedule_timezone(start + self.duration, time_zone))
event.add("dtstamp", self.rotation_start)
if self.event_ical_rules:
if custom_rrule:
event.add("rrule", custom_rrule)
elif self.event_ical_rules:
event.add("rrule", self.event_ical_rules)
try:
event_in_ical = event.to_ical().decode("utf-8")
@ -349,7 +421,7 @@ class CustomOnCallShift(models.Model):
summary += f"{user.username} "
return summary
def get_rotation_date(self, event_ical, get_next_date=False):
def get_rotation_date(self, event_ical, get_next_date=False, interval=None):
"""Get date of the next event (for rolling_users shifts)"""
ONE_DAY = 1
ONE_HOUR = 1
@ -363,7 +435,8 @@ class CustomOnCallShift(models.Model):
current_event = Event.from_ical(event_ical)
# take shift interval, not event interval. For rolling_users shift it is not the same.
interval = self.interval or 1
if interval is None:
interval = self.interval or 1
if "rrule" in current_event:
# when triggering shift previews, there could be no rrule information yet
# (e.g. initial empty weekly rotation has no rrule set)

View file

@ -336,13 +336,15 @@ class OnCallSchedule(PolymorphicModel):
resolved.append(ev)
continue
if ev["priority_level"] != current_priority:
# api/terraform shifts could be missing a priority; assume None means 0
priority = ev["priority_level"] or 0
if priority != current_priority:
# update scheduled intervals on priority change
# and start from the beginning for the new priority level
resolved.sort(key=event_start_cmp_key)
intervals = _merge_intervals(resolved)
current_interval_idx = 0
current_priority = ev["priority_level"]
current_priority = priority
if current_interval_idx >= len(intervals):
# event outside scheduled intervals, add to resolved
@ -406,8 +408,10 @@ class OnCallSchedule(PolymorphicModel):
and current["shift"]["pk"] is not None
and current["shift"]["pk"] == next_event["shift"]["pk"]
):
current["users"] += next_event["users"]
current["missing_users"] += next_event["missing_users"]
current["users"] += [u for u in next_event["users"] if u not in current["users"]]
current["missing_users"] += [
u for u in next_event["missing_users"] if u not in current["missing_users"]
]
else:
merged.append(next_event)
current = next_event

View file

@ -293,6 +293,146 @@ def test_rolling_users_event_with_interval_daily(
assert len(users_on_call) == 0
@pytest.mark.django_db
def test_rolling_users_event_daily_by_day(
make_organization_and_user, make_user_for_organization, make_on_call_shift, make_schedule
):
organization, user_1 = make_organization_and_user()
user_2 = make_user_for_organization(organization)
schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb)
now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
today_weekday = now.weekday()
delta_days = (0 - today_weekday) % 7 + (7 if today_weekday == 0 else 0)
next_week_monday = now + timezone.timedelta(days=delta_days)
# MO, WE, FR
weekdays = [0, 2, 4]
by_day = [CustomOnCallShift.ICAL_WEEKDAY_MAP[day] for day in weekdays]
data = {
"priority_level": 1,
"start": next_week_monday,
"rotation_start": next_week_monday,
"duration": timezone.timedelta(seconds=10800),
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
"interval": 1,
"by_day": by_day,
"schedule": schedule,
}
rolling_users = [[user_1], [user_2]]
on_call_shift = make_on_call_shift(
organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
)
on_call_shift.add_rolling_users(rolling_users)
date = next_week_monday + timezone.timedelta(minutes=5)
user_1_on_call_dates = [date, date + timezone.timedelta(days=4), date + timezone.timedelta(days=9)]
user_2_on_call_dates = [date + timezone.timedelta(days=2), date + timezone.timedelta(days=7)]
nobody_on_call_dates = [
date + timezone.timedelta(days=1), # TU
date + timezone.timedelta(days=3), # TH
date + timezone.timedelta(days=5), # SAT
date + timezone.timedelta(days=6), # SUN
date + timezone.timedelta(days=8), # TU
date + timezone.timedelta(days=10), # TH
date + timezone.timedelta(days=12), # SAT
]
for dt in user_1_on_call_dates:
users_on_call = list_users_to_notify_from_ical(schedule, dt)
assert len(users_on_call) == 1
assert user_1 in users_on_call
for dt in user_2_on_call_dates:
users_on_call = list_users_to_notify_from_ical(schedule, dt)
assert len(users_on_call) == 1
assert user_2 in users_on_call
for dt in nobody_on_call_dates:
users_on_call = list_users_to_notify_from_ical(schedule, dt)
assert len(users_on_call) == 0
@pytest.mark.django_db
def test_rolling_users_event_with_interval_daily_by_day(
make_organization_and_user, make_user_for_organization, make_on_call_shift, make_schedule
):
organization, user_1 = make_organization_and_user()
user_2 = make_user_for_organization(organization)
schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb)
now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
today_weekday = now.weekday()
delta_days = (0 - today_weekday) % 7 + (7 if today_weekday == 0 else 0)
next_week_monday = now + timezone.timedelta(days=delta_days)
# MO, WE, FR
weekdays = [0, 2, 4]
by_day = [CustomOnCallShift.ICAL_WEEKDAY_MAP[day] for day in weekdays]
data = {
"priority_level": 1,
"start": next_week_monday,
"rotation_start": next_week_monday,
"duration": timezone.timedelta(seconds=10800),
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
"interval": 2,
"by_day": by_day,
"schedule": schedule,
}
rolling_users = [[user_1], [user_2]]
on_call_shift = make_on_call_shift(
organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
)
on_call_shift.add_rolling_users(rolling_users)
date = next_week_monday + timezone.timedelta(minutes=5)
user_1_on_call_dates = [
date, # MO
date + timezone.timedelta(days=2), # WE
date + timezone.timedelta(days=9), # WE
date + timezone.timedelta(days=11), # FR
date + timezone.timedelta(days=18), # FR
date + timezone.timedelta(days=21), # MO
date + timezone.timedelta(days=28), # MO
date + timezone.timedelta(days=30), # WE
]
user_2_on_call_dates = [
date + timezone.timedelta(days=4), # FR
date + timezone.timedelta(days=7), # MO
date + timezone.timedelta(days=14), # MO
date + timezone.timedelta(days=16), # WE
date + timezone.timedelta(days=23), # WE
date + timezone.timedelta(days=25), # FR
date + timezone.timedelta(days=32), # FR
date + timezone.timedelta(days=35), # MO
]
nobody_on_call_dates = [
date + timezone.timedelta(days=1), # TU
date + timezone.timedelta(days=3), # TH
date + timezone.timedelta(days=5), # SAT
date + timezone.timedelta(days=6), # SUN
date + timezone.timedelta(days=8), # TU
date + timezone.timedelta(days=10), # TH
date + timezone.timedelta(days=12), # SAT
]
for dt in user_1_on_call_dates:
users_on_call = list_users_to_notify_from_ical(schedule, dt)
assert len(users_on_call) == 1
assert user_1 in users_on_call
for dt in user_2_on_call_dates:
users_on_call = list_users_to_notify_from_ical(schedule, dt)
assert len(users_on_call) == 1
assert user_2 in users_on_call
for dt in nobody_on_call_dates:
users_on_call = list_users_to_notify_from_ical(schedule, dt)
assert len(users_on_call) == 0
@pytest.mark.django_db
def test_rolling_users_event_with_interval_weekly(
make_organization_and_user, make_user_for_organization, make_on_call_shift, make_schedule

View file

@ -387,6 +387,86 @@ def test_final_schedule_events(make_organization, make_user_for_organization, ma
assert returned_events == expected_events
@pytest.mark.django_db
def test_final_schedule_override_no_priority_shift(
make_organization, make_user_for_organization, make_on_call_shift, make_schedule
):
organization = make_organization()
schedule = make_schedule(
organization,
schedule_class=OnCallScheduleWeb,
name="test_web_schedule",
)
now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
start_date = now - timezone.timedelta(days=7)
user_a, user_b = (make_user_for_organization(organization, username=i) for i in "AB")
# clear users pks <-> organization cache (persisting between tests)
memoized_users_in_ical.cache_clear()
shifts = (
# user, priority, start time (h), duration (hs)
(user_a, 0, 10, 5), # 10-15 / A
)
for user, priority, start_h, duration in shifts:
data = {
"start": start_date + timezone.timedelta(hours=start_h),
"rotation_start": start_date + timezone.timedelta(hours=start_h),
"duration": timezone.timedelta(hours=duration),
"priority_level": priority,
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
"schedule": schedule,
}
on_call_shift = make_on_call_shift(
organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
)
on_call_shift.add_rolling_users([[user]])
# override: 10-15 / B
override_data = {
"start": start_date + timezone.timedelta(hours=10),
"rotation_start": start_date + timezone.timedelta(hours=5),
"duration": timezone.timedelta(hours=5),
"schedule": schedule,
}
override = make_on_call_shift(
organization=organization, shift_type=CustomOnCallShift.TYPE_OVERRIDE, **override_data
)
override.add_rolling_users([[user_b]])
returned_events = schedule.final_events("UTC", start_date, days=1)
expected = (
# start (h), duration (H), user, priority, is_override
(10, 5, "B", None, True), # 10-15 B
)
expected_events = [
{
"calendar_type": 1 if is_override else 0,
"end": start_date + timezone.timedelta(hours=start + duration),
"is_override": is_override,
"priority_level": priority,
"start": start_date + timezone.timedelta(hours=start, milliseconds=1 if start == 0 else 0),
"user": user,
}
for start, duration, user, priority, is_override in expected
]
returned_events = [
{
"calendar_type": e["calendar_type"],
"end": e["end"],
"is_override": e["is_override"],
"priority_level": e["priority_level"],
"start": e["start"],
"user": e["users"][0]["display_name"] if e["users"] else None,
}
for e in returned_events
if not e["is_gap"]
]
assert returned_events == expected_events
@pytest.mark.django_db
def test_final_schedule_splitting_events(
make_organization, make_user_for_organization, make_on_call_shift, make_schedule

View file

@ -1,4 +1,4 @@
django==3.2.15
django==3.2.16
djangorestframework==3.12.4
slackclient==1.3.0
whitenoise==5.3.0

View file

@ -101,6 +101,7 @@ CELERY_TASK_ROUTES = {
"apps.alerts.tasks.unsilence.unsilence_task": {"queue": "critical"},
"apps.base.tasks.process_failed_to_invoke_celery_tasks": {"queue": "critical"},
"apps.base.tasks.process_failed_to_invoke_celery_tasks_batch": {"queue": "critical"},
"apps.email.tasks.notify_user_async": {"queue": "critical"},
"apps.integrations.tasks.create_alert": {"queue": "critical"},
"apps.integrations.tasks.create_alertmanager_alerts": {"queue": "critical"},
"apps.integrations.tasks.start_notify_about_integration_ratelimit": {"queue": "critical"},

View file

@ -0,0 +1,15 @@
{
"presets": [
["@babel/preset-env", { "targets": { "node": "current" } }],
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-transform-destructuring", { "useBuiltIns": true }],
"@babel/plugin-transform-runtime",
"@babel/proposal-class-properties",
"@babel/transform-regenerator",
"@babel/plugin-transform-template-literals",
]
}

View file

@ -1,29 +1,19 @@
const esModules = ['react-colorful', 'uuid', 'ol'].join('|');
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
moduleDirectories: ['node_modules', 'src'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
moduleFileExtensions: ['ts', 'tsx', 'js'],
globals: {
'ts-jest': {
isolatedModules: true,
babelConfig: true
},
},
transform: {
'^.+\\.js?$': require.resolve('babel-jest'),
'^.+\\.jsx?$': require.resolve('babel-jest'),
'^.+\\.ts?$': require.resolve('ts-jest'),
'^.+\\.tsx?$': require.resolve('ts-jest'),
},
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
moduleNameMapper: {
"grafana/app/(.*)": '<rootDir>/src/jest/grafanaMock.ts',
"jest/outgoingWebhooksStub": '<rootDir>/src/jest/outgoingWebhooksStub.ts',
"^jest$": '<rootDir>/src/jest',
'grafana/app/(.*)': '<rootDir>/src/jest/grafanaMock.ts',
'jest/matchMedia': '<rootDir>/src/jest/matchMedia.ts',
'jest/outgoingWebhooksStub': '<rootDir>/src/jest/outgoingWebhooksStub.ts',
'^jest$': '<rootDir>/src/jest',
'^.+\\.(css|scss)$': '<rootDir>/src/jest/styleMock.ts',
"^lodash-es$": "lodash",
}
};
'^lodash-es$': 'lodash',
},
};

View file

@ -40,27 +40,29 @@
"license": "Apache-2.0",
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.18.10",
"@babel/plugin-proposal-decorators": "^7.20.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
"@babel/plugin-proposal-object-rest-spread": "^7.18.9",
"@babel/plugin-proposal-optional-chaining": "^7.18.9",
"@babel/plugin-syntax-decorators": "^7.18.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-destructuring": "^7.20.0",
"@babel/plugin-transform-react-constant-elements": "^7.18.12",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/plugin-transform-typescript": "^7.18.12",
"@babel/preset-env": "^7.18.10",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@grafana/data": "^9.1.1",
"@grafana/data": "9.1.1",
"@grafana/eslint-config": "^5.0.0",
"@grafana/runtime": "^9.1.1",
"@grafana/toolkit": "^9.1.1",
"@grafana/ui": "^9.1.1",
"@jest/globals": "^27.5.1",
"@grafana/runtime": "9.1.1",
"@grafana/toolkit": "9.1.1",
"@grafana/ui": "9.1.1",
"@jest/globals": "27.5.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "12",
"@types/dompurify": "^2.3.4",
"@types/jest": "^27.5.1",
"@types/jest": "27.5.1",
"@types/lodash-es": "^4.17.6",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^18.0.6",
@ -69,6 +71,7 @@
"@types/react-test-renderer": "^17.0.2",
"@types/throttle-debounce": "^5.0.0",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"babel-plugin-dynamic-import-node": "^2.3.3",
"copy-webpack-plugin": "^11.0.0",
"dompurify": "^2.3.12",
"eslint": "^8.25.0",
@ -76,17 +79,19 @@
"eslint-plugin-react": "^7.31.10",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-rulesdir": "^0.2.1",
"jest": "^27.5.1",
"jest": "27.5.1",
"jest-environment-jsdom": "^27.5.1",
"lint-staged": "^10.2.11",
"lodash-es": "^4.17.21",
"moment-timezone": "^0.5.35",
"plop": "^2.7.4",
"postcss-loader": "^7.0.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-test-renderer": "^17.0.2",
"stylelint-config-prettier": "^9.0.3",
"stylelint-prettier": "^2.0.0",
"ts-jest": "^27.1.3",
"ts-jest": "29.0.3",
"ts-loader": "^9.3.1",
"typescript": "4.6.4",
"webpack-bundle-analyzer": "^4.6.1"

View file

@ -0,0 +1,31 @@
import React from 'react';
import { PENDING_COLOR, Tooltip, Icon } from '@grafana/ui';
import { Schedule } from 'models/schedule/schedule.types';
interface ScheduleWarningProps {
item: Schedule;
}
const ScheduleWarning = (props: ScheduleWarningProps) => {
const { item } = props;
if (item.warnings.length > 0) {
const tooltipContent = (
<div>
{item.warnings.map((warning: string, key: number) => (
<p key={key}>{warning}</p>
))}
</div>
);
return (
<Tooltip placement="top" content={tooltipContent}>
<Icon style={{ color: PENDING_COLOR }} name="exclamation-triangle" />
</Tooltip>
);
}
return null;
};
export default ScheduleWarning;

View file

@ -68,9 +68,8 @@ const SchedulesFilters = (props: SchedulesFiltersProps) => {
</Field>
<Field label="Type">
<RadioButtonGroup
disabled
options={[
{ label: 'All', value: 'all' },
{ label: 'All', value: undefined },
{
label: 'Web',
value: ScheduleType.API,
@ -84,7 +83,7 @@ const SchedulesFilters = (props: SchedulesFiltersProps) => {
value: ScheduleType.Calendar,
},
]}
value={value.type}
value={value?.type}
onChange={handleTypeChange}
/>
</Field>

View file

@ -154,7 +154,7 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
rolling_users: userGroups,
interval: repeatEveryValue,
frequency: repeatEveryPeriod,
by_day: repeatEveryPeriod === 1 ? selectedDays : null,
by_day: repeatEveryPeriod === 1 || repeatEveryPeriod === 0 ? selectedDays : null,
priority_level: shiftId === 'new' ? layerPriority : shift?.priority_level,
}),
[
@ -320,7 +320,7 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
/>
</Field>
</HorizontalGroup>
{repeatEveryPeriod === 1 && (
{(repeatEveryPeriod === 1 || repeatEveryPeriod === 0) && (
<Field label="Select days to repeat">
<DaysSelector
options={store.scheduleStore.byDayOptions}

View file

@ -18,7 +18,7 @@ import { Rotation, RotationType, Schedule, ScheduleEvent, Shift, Event, Layer, S
export class ScheduleStore extends BaseStore {
@observable
searchResult: { [key: string]: Array<Schedule['id']> } = {};
searchResult: { results?: Array<Schedule['id']> } = {};
@observable.shallow
items: { [id: string]: Schedule } = {};
@ -105,8 +105,11 @@ export class ScheduleStore extends BaseStore {
}
@action
async updateItems(query = '') {
const result = await makeRequest(this.path, { method: 'GET', params: { search: query } });
async updateItems(f: any = { searchTerm: '', type: undefined }) {
// async updateItems(query = '') {
const filters = typeof f === 'string' ? { searchTerm: f } : f;
const { searchTerm: search, type } = filters;
const result = await makeRequest(this.path, { method: 'GET', params: { search: search, type } });
this.items = {
...this.items,
@ -118,10 +121,9 @@ export class ScheduleStore extends BaseStore {
{}
),
};
this.searchResult = {
...this.searchResult,
[query]: result.map((item: Schedule) => item.id),
results: result.map((item: Schedule) => item.id),
};
}
@ -136,12 +138,11 @@ export class ScheduleStore extends BaseStore {
}
}
getSearchResult(query = '') {
if (!this.searchResult[query]) {
getSearchResult() {
if (!this.searchResult.results) {
return undefined;
}
return this.searchResult[query].map((scheduleId: Schedule['id']) => this.items[scheduleId]);
return this.searchResult?.results?.map((scheduleId: Schedule['id']) => this.items[scheduleId]);
}
@action

View file

@ -7,8 +7,7 @@ import '@testing-library/jest-dom';
import outgoingWebhooksStub from 'jest/outgoingWebhooksStub';
import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types';
import { OutgoingWebhooks } from './OutgoingWebhooks';
import { OutgoingWebhooks } from 'pages/outgoing_webhooks/OutgoingWebhooks';
const outgoingWebhooks = outgoingWebhooksStub as OutgoingWebhook[];
const outgoingWebhookStore = () => ({
@ -21,12 +20,21 @@ const outgoingWebhookStore = () => ({
}, {}),
});
jest.mock('@grafana/runtime', () => ({
config: {
featureToggles: {
topNav: false,
},
},
}));
jest.mock('state/useStore', () => ({
useStore: () => ({
outgoingWebhookStore: outgoingWebhookStore(),
isUserActionAllowed: jest.fn().mockReturnValue(true),
}),
}));
jest.mock('@grafana/runtime', () => ({
getLocationSrv: jest.fn(),
}));

View file

@ -2,12 +2,14 @@ import React from 'react';
import { AppRootProps } from '@grafana/data';
import { getLocationSrv } from '@grafana/runtime';
import { Button, HorizontalGroup, VerticalGroup, IconButton, ToolbarButton, Icon } from '@grafana/ui';
import { Button, HorizontalGroup, VerticalGroup, IconButton, ToolbarButton, Icon, Modal } from '@grafana/ui';
import cn from 'classnames/bind';
import dayjs from 'dayjs';
import { omit } from 'lodash-es';
import { observer } from 'mobx-react';
import PluginLink from 'components/PluginLink/PluginLink';
import ScheduleWarning from 'components/ScheduleWarning/ScheduleWarning';
import Text from 'components/Text/Text';
import UserTimezoneSelect from 'components/UserTimezoneSelect/UserTimezoneSelect';
import WithConfirm from 'components/WithConfirm/WithConfirm';
@ -15,8 +17,9 @@ import Rotations from 'containers/Rotations/Rotations';
import ScheduleFinal from 'containers/Rotations/ScheduleFinal';
import ScheduleOverrides from 'containers/Rotations/ScheduleOverrides';
import ScheduleForm from 'containers/ScheduleForm/ScheduleForm';
import ScheduleICalSettings from 'containers/ScheduleIcalLink/ScheduleIcalLink';
import UsersTimezones from 'containers/UsersTimezones/UsersTimezones';
import { Shift } from 'models/schedule/schedule.types';
import { Schedule, ScheduleType, Shift } from 'models/schedule/schedule.types';
import { Timezone } from 'models/timezone/timezone.types';
import { WithStoreProps } from 'state/types';
import { withMobXProviderContext } from 'state/withStore';
@ -24,7 +27,6 @@ import { withMobXProviderContext } from 'state/withStore';
import { getStartOfWeek } from './Schedule.helpers';
import styles from './Schedule.module.css';
const cx = cn.bind(styles);
interface SchedulePageProps extends AppRootProps, WithStoreProps {}
@ -37,6 +39,7 @@ interface SchedulePageState {
shiftIdToShowOverridesForm?: Shift['id'];
isLoading: boolean;
showEditForm: boolean;
showScheduleICalSettings: boolean;
}
@observer
@ -53,6 +56,7 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
shiftIdToShowOverridesForm: undefined,
isLoading: true,
showEditForm: false,
showScheduleICalSettings: false,
};
}
@ -89,6 +93,7 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
shiftIdToShowRotationForm,
shiftIdToShowOverridesForm,
showEditForm,
showScheduleICalSettings,
} = this.state;
const { scheduleStore, currentTimezone } = store;
@ -109,6 +114,7 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
<Text.Title editable editModalTitle="Schedule name" level={2} onTextChange={this.handleNameChange}>
{schedule?.name}
</Text.Title>
{schedule && <ScheduleWarning item={schedule} />}
</HorizontalGroup>
<HorizontalGroup spacing="lg">
{users && (
@ -118,6 +124,16 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
</HorizontalGroup>
)}
<HorizontalGroup>
{schedule?.type === ScheduleType.Ical && (
<HorizontalGroup>
<Button variant="secondary" onClick={this.handleExportClick()}>
Export
</Button>
<Button variant="secondary" onClick={this.handleReloadClick(scheduleId)}>
Reload
</Button>
</HorizontalGroup>
)}
<ToolbarButton
icon="cog"
tooltip="Settings"
@ -206,6 +222,16 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
}}
/>
)}
{showScheduleICalSettings && (
<Modal
isOpen
title="Schedule export"
closeOnEscape
onDismiss={() => this.setState({ showScheduleICalSettings: false })}
>
<ScheduleICalSettings id={scheduleId} />
</Modal>
)}
</>
);
}
@ -371,6 +397,47 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
this.setState({ startMoment: startMoment.add(7, 'day') }, this.handleDateRangeUpdate);
};
handleExportClick = () => {
return () => {
this.setState({ showScheduleICalSettings: true });
};
};
handleReloadClick = (scheduleId: Schedule['id']) => {
const { store } = this.props;
const { scheduleStore } = store;
return async () => {
await scheduleStore.reloadIcal(scheduleId);
scheduleStore.updateItem(scheduleId);
this.updateEventsFor(scheduleId);
};
};
updateEventsFor = async (scheduleId: Schedule['id'], withEmpty = true, with_gap = true) => {
const {
store,
query: { id },
} = this.props;
const { scheduleStore } = store;
store.scheduleStore.scheduleToScheduleEvents = omit(store.scheduleStore.scheduleToScheduleEvents, [scheduleId]);
await scheduleStore.updateScheduleEvents(
scheduleId,
withEmpty,
with_gap,
dayjs().format('YYYY-MM-DD').toString(),
dayjs.tz.guess()
);
await store.scheduleStore.updateOncallShifts(id);
await this.updateEvents();
};
handleDelete = () => {
const {
store,

View file

@ -11,6 +11,7 @@ import Avatar from 'components/Avatar/Avatar';
import NewScheduleSelector from 'components/NewScheduleSelector/NewScheduleSelector';
import PluginLink from 'components/PluginLink/PluginLink';
import ScheduleCounter from 'components/ScheduleCounter/ScheduleCounter';
import ScheduleWarning from 'components/ScheduleWarning/ScheduleWarning';
import SchedulesFilters from 'components/SchedulesFilters_NEW/SchedulesFilters';
import { SchedulesFiltersType } from 'components/SchedulesFilters_NEW/SchedulesFilters.types';
import Table from 'components/Table/Table';
@ -51,7 +52,7 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
const { store } = this.props;
this.state = {
startMoment: getStartOfWeek(store.currentTimezone),
filters: { searchTerm: '', status: 'all', type: ScheduleType.API },
filters: { searchTerm: '', status: 'all', type: undefined },
showNewScheduleSelector: false,
expandedRowKeys: [],
scheduleIdToEdit: undefined,
@ -80,10 +81,10 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
render: this.renderType,
},
{
width: '10%',
width: '5%',
title: 'Status',
key: 'name',
render: this.renderStatus,
render: (item: Schedule) => this.renderStatus(item),
},
{
width: '30%',
@ -107,6 +108,11 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
title: 'Slack user group',
render: this.renderUserGroup,
},
{
width: '5%',
key: 'warning',
render: this.renderWarning,
},
{
width: '50px',
key: 'buttons',
@ -119,7 +125,6 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
const data = schedules
? schedules
.filter((schedule) => schedule.type === ScheduleType.API)
.filter(
(schedule) =>
filters.status === 'all' ||
@ -265,38 +270,52 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
return typeToVerbal[value];
};
renderWarning = (item: Schedule) => {
return <ScheduleWarning item={item} />;
};
renderStatus = (item: Schedule) => {
const {
store: { scheduleStore },
} = this.props;
const relatedEscalationChains = scheduleStore.relatedEscalationChains[item.id];
return (
<HorizontalGroup>
<ScheduleCounter
type="link"
count={item.number_of_escalation_chains}
tooltipTitle="Used in escalations"
tooltipContent={
<VerticalGroup spacing="sm">
{relatedEscalationChains ? (
relatedEscalationChains.length ? (
relatedEscalationChains.map((escalationChain) => (
<PluginLink key={escalationChain.pk} query={{ page: 'escalations', id: escalationChain.pk }}>
{escalationChain.name}
</PluginLink>
))
{item.number_of_escalation_chains > 0 && (
<ScheduleCounter
type="link"
count={item.number_of_escalation_chains}
tooltipTitle="Used in escalations"
tooltipContent={
<VerticalGroup spacing="sm">
{relatedEscalationChains ? (
relatedEscalationChains.length ? (
relatedEscalationChains.map((escalationChain) => (
<div key={escalationChain.pk}>
<PluginLink query={{ page: 'escalations', id: escalationChain.pk }}>
{escalationChain.name}
</PluginLink>
</div>
))
) : (
'Not used yet'
)
) : (
'Not used yet'
)
) : (
<LoadingPlaceholder>Loading related escalation chains....</LoadingPlaceholder>
)}
</VerticalGroup>
}
onHover={this.getUpdateRelatedEscalationChainsHandler(item.id)}
/>
<LoadingPlaceholder>Loading related escalation chains....</LoadingPlaceholder>
)}
</VerticalGroup>
}
onHover={this.getUpdateRelatedEscalationChainsHandler(item.id)}
/>
)}
{/* <ScheduleCounter
type="warning"
count={warningsCount}
tooltipTitle="Warnings"
tooltipContent="Schedule has unassigned time periods during next 7 days"
/>*/}
</HorizontalGroup>
);
};
@ -372,9 +391,10 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
};
applyFilters = () => {
// const { filters } = this.state;
// const { scheduleStore } = this.props.store;
// scheduleStore.updateItems(filters.searchTerm);
const { filters } = this.state;
const { store } = this.props;
const { scheduleStore } = store;
scheduleStore.updateItems(filters);
};
debouncedUpdateSchedules = debounce(this.applyFilters, 1000);

File diff suppressed because it is too large Load diff

View file

@ -8,13 +8,13 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 1.0.8
version: 1.0.9
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "v1.0.49"
appVersion: "v1.0.50"
dependencies:
- name: cert-manager
version: v1.8.0

View file

@ -237,15 +237,15 @@
{{- define "snippet.rabbitmq.env" -}}
{{- if eq .Values.broker.type "rabbitmq" -}}
{{- if and (not .Values.rabbitmq.enabled) (not .Values.externalRabbitmq.existingSecret) (not .Values.externalRabbitmq.usernameKey) .Values.externalRabbitmq.user }}
- name: RABBITMQ_USERNAME
value: {{ include "snippet.rabbitmq.user" . }}
{{- else if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.existingSecret .Values.externalRabbitmq.usernameKey (not .Values.externalRabbitmq.user) }}
{{- if and (not .Values.rabbitmq.enabled) .Values.externalRabbitmq.existingSecret .Values.externalRabbitmq.usernameKey (not .Values.externalRabbitmq.user) }}
- name: RABBITMQ_USERNAME
valueFrom:
secretKeyRef:
name: {{ include "snippet.rabbitmq.password.secret.name" . }}
key: {{ .Values.externalRabbitmq.usernameKey }}
{{- else }}
- name: RABBITMQ_USERNAME
value: {{ include "snippet.rabbitmq.user" . }}
{{- end }}
- name: RABBITMQ_PASSWORD
valueFrom: