add support for logging in to mobile using deep links (#4212)

# What this PR does

Optimize mobile app connection layout for smaller screens and add
LinkLoginButton component


https://www.loom.com/share/1e057464bbe6428c994cf4831713d45c?sid=b5d70863-862e-4ef9-8303-a3354622f442

## Which issue(s) this PR closes

https://github.com/grafana/oncall-private/issues/2528

## Checklist

- [x] 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.
This commit is contained in:
Salvatore Giordano 2024-04-12 15:43:51 +02:00 committed by GitHub
parent b3c1800f87
commit 395335f21d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 130 additions and 9 deletions

View file

@ -29,10 +29,21 @@ Mobile app download:
- [Google Play Store](https://play.google.com/store/apps/details?id=com.grafana.oncall.prod)
- [Apple App Store](https://apps.apple.com/us/app/grafana-oncall-preview/id1669759048)
## Connect your Grafana OnCall account
## Connect your Grafana OnCall account using deeplink authentication
The OnCall mobile app uses a QR code authentication to connect to your Grafana OnCall instance.
You can associate one Grafana OnCall user with your OnCall mobile app.
You can connect your Grafana OnCall account to the mobile app using a deeplink authentication.
This method is useful because it allows you to connect your account using only your mobile device.
To connect your account in the mobile app:
1. Open Grafana OnCall from your mobile device
2. Click on your profile icon in the top right corner
3. Click on the **IRM** tab
4. Click on the **Sign in** button
## Connect your Grafana OnCall account using QR code authentication
Another way to connect your Grafana OnCall account to the mobile app is by using a QR code authentication.
To connect your account in the mobile app:
@ -48,8 +59,8 @@ To connect your account in the mobile app:
To access your QR code:
1. Open Grafana OnCall from your desktop
1. Navigate to the **Users** tab, then tap **View my profile**
1. tap **Mobile app connection** in your profile
2. Click on your profile icon in the top right corner
3. Click on the **IRM** tab
> **Note**: The QR code will timeout for security purposes - Screenshots of the QR code are unlikely to work for authentication.

View file

@ -16,6 +16,22 @@
}
}
@media (max-width: 768px) {
.container {
flex-direction: column;
&__box:first-child {
margin-right: 0px;
margin-bottom: 8px;
}
&__box:last-child {
margin-left: 0px;
margin-top: 8px;
}
}
}
.notification-buttons {
width: 100%;
padding-top: 12px;

View file

@ -14,11 +14,12 @@ import { ApiSchemas } from 'network/oncall-api/api.types';
import { AppFeature } from 'state/features';
import { RootStore, rootStore as store } from 'state/rootStore';
import { UserActions } from 'utils/authorization/authorization';
import { openErrorNotification, openNotification, openWarningNotification } from 'utils/utils';
import { isMobile, openErrorNotification, openNotification, openWarningNotification } from 'utils/utils';
import styles from './MobileAppConnection.module.scss';
import { DisconnectButton } from './parts/DisconnectButton/DisconnectButton';
import { DownloadIcons } from './parts/DownloadIcons/DownloadIcons';
import { LinkLoginButton } from './parts/LinkLoginButton/LinkLoginButton';
import { QRCode } from './parts/QRCode/QRCode';
const cx = cn.bind(styles);
@ -143,6 +144,7 @@ export const MobileAppConnection = observer(({ userPk }: Props) => {
}
let content: React.ReactNode = null;
const QRCodeDataParsed = QRCodeValue && getParsedQRCodeValue();
if (fetchingQRCode || disconnectingMobileApp || !userPk || !basicDataLoaded) {
content = <LoadingPlaceholder text="Loading..." />;
@ -165,12 +167,10 @@ export const MobileAppConnection = observer(({ userPk }: Props) => {
</VerticalGroup>
);
} else if (QRCodeValue) {
const QRCodeDataParsed = getParsedQRCodeValue();
content = (
<VerticalGroup spacing="lg">
<Text type="primary" strong>
Sign In
Sign in via QR Code
</Text>
<Text type="primary">
Open the Grafana OnCall mobile application and scan this code to sync it with your account.
@ -198,6 +198,11 @@ export const MobileAppConnection = observer(({ userPk }: Props) => {
<Block shadowed bordered withBackground className={cx('container__box')}>
<DownloadIcons />
</Block>
{QRCodeDataParsed && isMobile && (
<Block shadowed bordered withBackground className={cx('container__box')}>
<LinkLoginButton baseUrl={QRCodeDataParsed.oncall_api_url} token={QRCodeDataParsed.token} />
</Block>
)}
<Block shadowed bordered withBackground className={cx('container__box')}>
{content}
</Block>

View file

@ -0,0 +1,12 @@
import React from 'react';
import { render } from '@testing-library/react';
import { LinkLoginButton } from './LinkLoginButton';
describe('LinkLoginButton', () => {
test('it renders properly', () => {
const component = render(<LinkLoginButton baseUrl="http://test.url" token="test1213" />);
expect(component.container).toMatchSnapshot();
});
});

View file

@ -0,0 +1,32 @@
import React, { FC } from 'react';
import { Button, VerticalGroup } from '@grafana/ui';
import { Text } from 'components/Text/Text';
type Props = {
baseUrl: string;
token: string;
};
export const LinkLoginButton: FC<Props> = (props: Props) => {
const { baseUrl, token } = props;
const mobileDeepLink = `grafana://mobile/login/link-login?oncall_api_url=${baseUrl}&token=${token}`;
return (
<VerticalGroup spacing="lg">
<Text type="primary" strong>
Sign in via deeplink
</Text>
<Text type="primary">Make sure to have the app installed</Text>
<Button
variant="primary"
onClick={() => {
window.open(mobileDeepLink, '_blank');
}}
>
Sign in
</Button>
</VerticalGroup>
);
};

View file

@ -0,0 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LinkLoginButton it renders properly 1`] = `
<div>
<div
class="css-gjl87o-vertical-group"
style="width: 100%; height: 100%;"
>
<div
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-77ouhj--strong css-1d6rs0l"
>
Sign in via deeplink
</span>
</div>
<div
class="css-12oo3x0-layoutChildrenWrapper"
>
<span
class="css-77ouhj--primary css-77ouhj--medium css-1d6rs0l"
>
Make sure to have the app installed
</span>
</div>
<div
class="css-12oo3x0-layoutChildrenWrapper"
>
<button
class="css-td06pi-button"
type="button"
>
<span
class="css-1riaxdn"
>
Sign in
</span>
</button>
</div>
</div>
</div>
`;

View file

@ -116,3 +116,5 @@ function isFieldEmpty(value: any): boolean {
}
export const allFieldsEmpty = (obj: any) => every(obj, isFieldEmpty);
export const isMobile = window.matchMedia('(max-width: 768px)').matches;