Google Calendar integration improvements (#4147)

# What this PR does

- UI enhancements
- Fix bug when going through the Google OAuth2 disconnection flow. We
should send the `refresh_token` in the `revoke` HTTP request, rather
than the `access_token` (`access_token` expires frequently and can
result in Google sending back HTTP 400.. `refresh_token` is long lived
and can also be sent in the revoke flow)

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.

---------

Co-authored-by: Maxim Mordasov <maxim.mordasov@grafana.com>
This commit is contained in:
Joey Orlando 2024-04-04 12:03:40 -04:00 committed by GitHub
parent 4c79d69c17
commit c6f5c9b14d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 22 deletions

View file

@ -22,6 +22,16 @@ def persist_access_and_refresh_tokens(backend: typing.Type[BaseAuth], response:
def disconnect_user_google_oauth2_settings(backend: typing.Type[BaseAuth], user: User, *args, **kwargs):
"""
Don't use `google_oauth2_user.access_token` when revoking token, use `refresh_token` instead. If we use
the access token, we may get an HTTP 400 from Google because the token may be invalid or revoked.
https://stackoverflow.com/a/18578660/3902555
"""
logger.info(f"Disconnecting user {user.pk} from Google OAuth2")
# 2nd argument, uid, is not needed for GoogleOauth2 backend
backend.revoke_token(user.google_oauth2_user.access_token, "")
backend.revoke_token(user.google_oauth2_user.refresh_token, "")
user.finish_google_oauth2_disconnection_flow()
logger.info(f"Successfully disconnected user {user.pk} from Google OAuth2")

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { css } from '@emotion/css';
import { Button, HorizontalGroup, InlineSwitch, VerticalGroup, useStyles2 } from '@grafana/ui';
import { Button, HorizontalGroup, Switch, VerticalGroup, useStyles2 } from '@grafana/ui';
import { observer } from 'mobx-react';
import { Block } from 'components/GBlock/Block';
@ -21,11 +21,16 @@ const GoogleCalendar: React.FC<{ id: ApiSchemas['User']['pk'] }> = observer(({ i
const styles = useStyles2(getStyles);
const user = userStore.items[id];
const [googleCalendarSettings, setGoogleCalendarSettings] = useState(user?.google_calendar_settings);
const [showSchedulesDropdown, setShowSchedulesDropdown] = useState(
user.google_calendar_settings?.oncall_schedules_to_consider_for_shift_swaps?.length > 0
);
const [googleCalendarSettings, setGoogleCalendarSettings] =
useState<ApiSchemas['User']['google_calendar_settings']>();
const [showSchedulesDropdown, setShowSchedulesDropdown] = useState<boolean>();
useEffect(() => {
userStore.fetchItemById({ userPk: id }).then((user) => {
setGoogleCalendarSettings(user.google_calendar_settings);
setShowSchedulesDropdown(user.google_calendar_settings.oncall_schedules_to_consider_for_shift_swaps?.length > 0);
});
}, []);
const handleShowSchedulesDropdownChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.checked;
@ -36,12 +41,6 @@ const GoogleCalendar: React.FC<{ id: ApiSchemas['User']['pk'] }> = observer(({ i
}
};
useEffect(() => {
if (user) {
setGoogleCalendarSettings(user.google_calendar_settings);
}
}, [user]);
const handleSchedulesChange = (value) => {
setGoogleCalendarSettings((v) => ({ ...v, oncall_schedules_to_consider_for_shift_swaps: value }));
@ -50,10 +49,12 @@ const GoogleCalendar: React.FC<{ id: ApiSchemas['User']['pk'] }> = observer(({ i
});
};
const user = userStore.items[id];
return (
<VerticalGroup>
<Block bordered className={styles.root}>
<VerticalGroup>
<VerticalGroup spacing="lg">
{user.has_google_oauth2_connected ? (
<VerticalGroup>
<HorizontalGroup justify="space-between">
@ -71,7 +72,7 @@ const GoogleCalendar: React.FC<{ id: ApiSchemas['User']['pk'] }> = observer(({ i
</HorizontalGroup>
</VerticalGroup>
) : (
<HorizontalGroup justify="space-between">
<HorizontalGroup justify="space-between" spacing="lg">
<HorizontalGroup spacing="md">
<GoogleCalendarLogo width={32} height={32} />
<div>
@ -93,13 +94,10 @@ const GoogleCalendar: React.FC<{ id: ApiSchemas['User']['pk'] }> = observer(({ i
{user.has_google_oauth2_connected && (
<VerticalGroup>
<WithPermissionControlTooltip userAction={UserActions.UserSettingsWrite}>
<InlineSwitch
showLabel
label="Specify the schedules to sync with Google calendar"
value={showSchedulesDropdown}
transparent
onChange={handleShowSchedulesDropdownChange}
/>
<HorizontalGroup align="center">
<Switch value={showSchedulesDropdown} onChange={handleShowSchedulesDropdownChange} />
<Text type="secondary">Specify the schedules to sync with Google calendar</Text>
</HorizontalGroup>
</WithPermissionControlTooltip>
{showSchedulesDropdown && (
<div style={{ width: '100%' }}>