Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alerting: Adds support for timezones in mute timings #68813

Merged
merged 13 commits into from
May 25, 2023

Conversation

gillesdemey
Copy link
Member

@gillesdemey gillesdemey commented May 22, 2023

What is this feature?

This PR adds several improvements to mute timings:

  1. Support for timezones / locations
  2. Better visual selector for week days
  3. Improvements to loading / error handling
  4. Better validation for timeranges
image

Which issue(s) does this PR fix?:

Fixes #48508

Special notes for your reviewer:

Still needs to write a few tests for this one, but it's ready for a functionality and code review.

Future improvements

While working on this I'm thinking the following improvements might be interesting;

  • Show how long until the next mute timing
  • Calculate how long the mute timing lasts
  • Show a calendar type of visualizations for a mute timing

@@ -631,6 +631,7 @@ muteTimes:
- times:
- start_time: '06:00'
end_time: '23:59'
location: 'UTC'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this needs some comment about what timezone syntax is acceptable?

}

const useDefaultValues = (muteTiming?: MuteTimeInterval): MuteTimingFields => {
return useMemo(() => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diff here looks a bit weird but removed the unnecessary useMemo

};

const defaultPageNav: Partial<NavModelItem> = {
icon: 'sitemap',
};

const MuteTimingForm = ({ muteTiming, showError, provenance }: Props) => {
const MuteTimingForm = ({ muteTiming, showError, loading, provenance }: Props) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loading is now being passed in to the form because for some reason the form contains the AlertingPageWrapper. We were showing multiple loaders before and one of them was even rendered entirely outside of the application.

})
);

setUpdating(true);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're disabling the form when the AM is still updating with changes – previously we immediately redirected and hoped it would have saved the changes.

</div>
);
})}
<Stack direction="column" gap={2}>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another weird diff; this time because I'm wrapping the intervals in a <Stack /> instead of applying bottom margins. There are some important changes in here though so don't skip past this part of the review.

})}
width={50}
placeholder="Example: 1:3, may:august, december"
// @ts-ignore react-hook-form doesn't handle nested field arrays well
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll see // @ts-ignore in a few places – this basically comes down to react-hook-form being sort of clueless about the types of nested fields when using useFieldArray<>()

return DAYS_OF_THE_WEEK.slice(startIndex, endIndex + 1);
}

const DaysOfTheWeek = ({ defaultValue = '', onChange }: DaysOfTheWeekProps) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are for parsing the Alertmanager config for weekdays in to our visual weekday selector.

Valid config is for example monday, wednesday:friday sunday which translates to monday, wednesday, thursday, friday, sunday

if (!timeString) {
return true;
}
const [hour, minutes] = timeString.split(':').map((x) => parseInt(x, 10));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed all of this in favor of a regular expression I've copied from the Alertmanager source. Previous implementation contained small bugs and would allow invalid syntax.

@@ -186,26 +183,6 @@ function useColumns(alertManagerSourceName: string, hideActions = false, setMute
}, [alertManagerSourceName, setMuteTimingName, showActions, permissions]);
}

function renderTimeIntervals(timeIntervals: TimeInterval[]) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to utils

refetch?: boolean; // refetch config on success
}

export const updateAlertManagerConfigAction = createAsyncThunk<void, UpdateAlertManagerConfigActionOptions, {}>(
'unifiedalerting/updateAMConfig',
({ alertManagerSourceName, oldConfig, newConfig, successMessage, redirectPath, refetch }, thunkAPI): Promise<void> =>
({ alertManagerSourceName, oldConfig, newConfig, successMessage, redirectPath, redirectSearch, refetch }, thunkAPI): Promise<void> =>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added support for redirectSearch here so we don't lose the original search params when redirecting

@gillesdemey gillesdemey marked this pull request as ready for review May 22, 2023 13:52
@gillesdemey gillesdemey requested review from a team and brendamuir as code owners May 22, 2023 13:52
@brendamuir
Copy link
Contributor

Hi there-so we'd need to ask Deyan perhaps--but as a user I prefer the text to tell me what I have to enter in a field-so more actionable really - so "Enter a time inclusive xxxx" also I think we need full stops - it looks odd to me without - but again - not sure of Grafana rules on this exactly.

@@ -315,6 +315,8 @@ export interface TimeInterval {
days_of_month?: string[];
months?: string[];
years?: string[];
/** IANA TZ identifier like "Europe/Brussels", also supports "Local" or "UTC" */
location?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, location is a feature of Alertmanager?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return (
'Times: ' +
(times ? times?.map(({ start_time, end_time }) => `${start_time} - ${end_time} UTC`).join(' and ') : 'All')
(times
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we slightly refactor this method so it's more readable? Does it return the format required by Alertmanager?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nay, this is just our presentation of a time string so we can do whatever we want here.

I had initially wanted to refactor the way we render mute timing, making it more visual and showing both the next mute time start and duration but decided it's probably best if I don't make this PR too large.

I'll refactor this function a bit though because it's turning in to quite the monstrosity

@@ -92,8 +92,10 @@ export function recordToArray(record: Record<string, string>): Array<{ key: stri
return Object.entries(record).map(([key, value]) => ({ key, value }));
}

export function makeAMLink(path: string, alertManagerName?: string, options?: Record<string, string>): string {
type URLParamsLike = ConstructorParameters<typeof URLSearchParams>[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

// @ts-ignore typescript types here incorrect, sigh
const startDate = moment().startOf('day').add(startTime, timeUnit);
// @ts-ignore typescript types here incorrect, sigh
const endDate = moment().startOf('day').add(endTime, timeUnit);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe date-fns provides better types?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I didn't find a function in date-fns that can work with the HH:mm notation – I checked that library first before settling on moment :(

import { SelectableValue } from '@grafana/data';
import { Select, SelectCommonProps } from '@grafana/ui';

const TIMEZONES = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can take a look if Intl.supportedValuesOf('timeZone') could give you this list

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea! I was looking for it in the Inl namespace but didn't find that one – thanks!

Copy link
Contributor

@konrad147 konrad147 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Great work! 🎉

@gillesdemey gillesdemey merged commit 721d51a into main May 25, 2023
14 checks passed
@gillesdemey gillesdemey deleted the alerting/mute-timings-timezone branch May 25, 2023 14:19
@ricky-undeadcoders ricky-undeadcoders modified the milestones: 10.1.x, 10.1.0 Aug 1, 2023
@jagan985
Copy link

Is this live already? if yes, which version of Grafana is this pls?

@grobinson-grafana
Copy link
Contributor

Hi! 👋 It will be in Grafana 10.1, scheduled to be released next week!

@benoittgt
Copy link
Contributor

benoittgt commented Nov 2, 2023

@gillesdemey Thanks a lot for this.

Isn't an issue that it is still written in "The time inclusive of the starting time and exclusive of the end time in UTC" ?

Also is it possible to write a time range with a midnight overlap? To avoid having writing this.

Screenshot 2023-11-02 at 21 25 06

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Timezone support for mute timings
8 participants