Polish GCal UI texting (#4244)

# What this PR does

Polish GCal UI texting

## Which issue(s) this PR closes

-

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated
- [ ] Documentation added (or `pr:no public docs` PR label added if not
required)
- [ ] 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: Joey Orlando <joey.orlando@grafana.com>
This commit is contained in:
Maxim Mordasov 2024-04-18 15:36:45 +01:00 committed by GitHub
parent c8b37c7ad2
commit baef4e2642
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 132 additions and 102 deletions

View file

@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react';
import { css } from '@emotion/css';
import { Button, HorizontalGroup, Icon, Switch, VerticalGroup, useStyles2 } from '@grafana/ui';
import { Button, HorizontalGroup, Switch, VerticalGroup, useStyles2 } from '@grafana/ui';
import { observer } from 'mobx-react';
import { getUtilStyles } from 'styles/utils.styles';
import { Block } from 'components/GBlock/Block';
import { Text } from 'components/Text/Text';
@ -15,23 +16,20 @@ import { UserHelper } from 'models/user/user.helpers';
import { ApiSchemas } from 'network/oncall-api/api.types';
import { useStore } from 'state/useStore';
import { UserActions } from 'utils/authorization/authorization';
import { DOCS_ROOT } from 'utils/consts';
const GoogleCalendar: React.FC<{ id: ApiSchemas['User']['pk'] }> = observer(({ id }) => {
const { userStore, scheduleStore } = useStore();
const styles = useStyles2(getStyles);
const utils = useStyles2(getUtilStyles);
const [googleCalendarSettings, setGoogleCalendarSettings] =
useState<ApiSchemas['User']['google_calendar_settings']>();
const [showSchedulesDropdown, setShowSchedulesDropdown] = useState<boolean>();
const user = userStore.items[id];
useEffect(() => {
(async () => {
const user = await userStore.fetchItemById({ userPk: id });
setGoogleCalendarSettings(user.google_calendar_settings);
setShowSchedulesDropdown(user.google_calendar_settings.oncall_schedules_to_consider_for_shift_swaps?.length > 0);
})();
}, []);
setShowSchedulesDropdown(user.google_calendar_settings?.oncall_schedules_to_consider_for_shift_swaps?.length > 0);
}, [user]);
const handleShowSchedulesDropdownChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.checked;
@ -43,111 +41,142 @@ const GoogleCalendar: React.FC<{ id: ApiSchemas['User']['pk'] }> = observer(({ i
};
const handleSchedulesChange = (value) => {
setGoogleCalendarSettings((v) => ({ ...v, oncall_schedules_to_consider_for_shift_swaps: value }));
userStore.updateCurrentUser({
google_calendar_settings: { ...googleCalendarSettings, oncall_schedules_to_consider_for_shift_swaps: value },
userStore.updateUser({
pk: id,
google_calendar_settings: {
...user.google_calendar_settings,
oncall_schedules_to_consider_for_shift_swaps: value,
},
});
};
const user = userStore.items[id];
return (
<VerticalGroup>
<Block bordered className={styles.root}>
<VerticalGroup spacing="lg">
{user.has_google_oauth2_connected ? (
<VerticalGroup>
<HorizontalGroup justify="space-between">
<HorizontalGroup>
<GoogleCalendarLogo width={32} height={32} />
<Text>Google calendar is connected</Text>
</HorizontalGroup>
<WithPermissionControlTooltip userAction={UserActions.UserSettingsWrite}>
<WithConfirm title="Are you sure to disconnect your Google account?" confirmText="Disconnect">
<Button variant="destructive" onClick={userStore.disconnectGoogle}>
Disconnect
</Button>
</WithConfirm>
</WithPermissionControlTooltip>
</HorizontalGroup>
</VerticalGroup>
) : (
<HorizontalGroup justify="space-between" spacing="lg">
<HorizontalGroup spacing="md">
<GoogleCalendarLogo width={32} height={32} />
<div>
<Text.Title level={5}>Connect your Google Calendar</Text.Title>
<Text type="secondary">
This connection allows Grafana OnCall to read your Out of Office events and autogenerate Shift Swap
Requests
</Text>
</div>
</HorizontalGroup>
<Block bordered className={utils.width100}>
<VerticalGroup spacing="lg">
{user.has_google_oauth2_connected ? (
<VerticalGroup>
<HorizontalGroup justify="space-between" spacing="lg" align="flex-start">
<Heading connected />
<WithPermissionControlTooltip userAction={UserActions.UserSettingsWrite}>
<Button variant="primary" onClick={UserHelper.handleConnectGoogle}>
Connect
</Button>
<WithConfirm title="Are you sure to disconnect your Google account?" confirmText="Disconnect">
<Button variant="destructive" onClick={userStore.disconnectGoogle}>
Disconnect
</Button>
</WithConfirm>
</WithPermissionControlTooltip>
</HorizontalGroup>
)}
{user.has_google_oauth2_connected && (
<VerticalGroup>
<WithPermissionControlTooltip userAction={UserActions.UserSettingsWrite}>
<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%' }}>
<WithPermissionControlTooltip userAction={UserActions.UserSettingsWrite}>
<GSelect<Schedule>
isMulti
showSearch
allowClear
disabled={false}
items={scheduleStore.items}
fetchItemsFn={scheduleStore.updateItems}
fetchItemFn={scheduleStore.updateItem}
getSearchResult={scheduleStore.getSearchResult}
displayField="name"
valueField="id"
placeholder="Select Schedules"
value={googleCalendarSettings?.oncall_schedules_to_consider_for_shift_swaps}
onChange={handleSchedulesChange}
/>
</WithPermissionControlTooltip>
</div>
)}
</VerticalGroup>
)}
<HorizontalGroup spacing="sm">
<Icon name="info-circle" />
<Text type="secondary">
Grafana OnCall's use and transfer to any other app of information received from Google APIs will adhere to{' '}
<a
target="_blank"
rel="noreferrer"
href="https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes"
>
<Text type="link">Google API Services User Data Policy</Text>
</a>
, including the Limited Use requirements.
</Text>
</VerticalGroup>
) : (
<HorizontalGroup justify="space-between" spacing="lg" align="flex-start">
<Heading connected={false} />
<WithPermissionControlTooltip userAction={UserActions.UserSettingsWrite}>
<Button variant="primary" onClick={UserHelper.handleConnectGoogle}>
Connect
</Button>
</WithPermissionControlTooltip>
</HorizontalGroup>
</VerticalGroup>
</Block>
</VerticalGroup>
)}
{user.has_google_oauth2_connected && (
<VerticalGroup>
<WithPermissionControlTooltip userAction={UserActions.UserSettingsWrite}>
<HorizontalGroup spacing="md" align="center">
<Switch value={showSchedulesDropdown} onChange={handleShowSchedulesDropdownChange} />
<Text type="secondary">Specify the schedules to sync with Google calendar</Text>
</HorizontalGroup>
</WithPermissionControlTooltip>
{showSchedulesDropdown && (
<div className={utils.width100}>
<WithPermissionControlTooltip userAction={UserActions.UserSettingsWrite}>
<GSelect<Schedule>
isMulti
showSearch
allowClear
disabled={false}
items={scheduleStore.items}
fetchItemsFn={scheduleStore.updateItems}
fetchItemFn={scheduleStore.updateItem}
getSearchResult={scheduleStore.getSearchResult}
displayField="name"
valueField="id"
placeholder="Select Schedules"
value={user.google_calendar_settings.oncall_schedules_to_consider_for_shift_swaps}
onChange={handleSchedulesChange}
/>
</WithPermissionControlTooltip>
</div>
)}
</VerticalGroup>
)}
</VerticalGroup>
</Block>
);
});
export const getStyles = () => ({
root: css({
width: '100%',
icon: css({
marginTop: '6px',
}),
});
const Heading: React.FC<{ connected: boolean }> = ({ connected }) => {
const styles = useStyles2(getStyles);
return (
<HorizontalGroup spacing="md" align="flex-start">
<div className={styles.icon}>
<GoogleCalendarLogo width={32} height={32} />
</div>
<VerticalGroup spacing="md">
<VerticalGroup spacing="none">
<Text.Title level={5}>
{connected ? 'Google calendar is connected' : 'Connect your Google Calendar'}
</Text.Title>
{connected ? (
<Text type="secondary">
Add <Text type="primary">#grafana-oncall-ignore</Text> to an Out of Office event title to exclude it from
Shift Swap Request creation.{' '}
<a
href={`${DOCS_ROOT}/manage/on-call-schedules/shift-swaps/#google-calendar-integration`}
target="_blank"
rel="noreferrer"
>
<Text type="link">Read more</Text>
</a>
</Text>
) : (
<Text type="secondary">
This connection allows OnCall to read your Out of Office events and autogenerate Shift Swap Requests.{' '}
<a
href={`${DOCS_ROOT}/manage/on-call-schedules/shift-swaps/#google-calendar-integration`}
target="_blank"
rel="noreferrer"
>
<Text type="link">Read more</Text>
</a>
</Text>
)}
</VerticalGroup>
{!connected && (
<Text type="secondary">
Grafana OnCall's use and transfer to any other app of information received from Google APIs will adhere
<br />
to{' '}
<a
target="_blank"
rel="noreferrer"
href="https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes"
>
<Text type="secondary" underline>
Google API Services User Data Policy
</Text>
</a>
, including the Limited Use requirements.
</Text>
)}
</VerticalGroup>
</HorizontalGroup>
);
};
export { GoogleCalendar };

View file

@ -52,6 +52,7 @@ export class UserStore {
(acc: { [key: number]: ApiSchemas['User'] }, item: ApiSchemas['User']) => ({
...acc,
[item.pk]: {
...this.items[item.pk],
...item,
timezone: UserHelper.getTimezone(item),
},