diff --git a/docs/sources/manage/mobile-app/installation-and-setup/index.md b/docs/sources/manage/mobile-app/installation-and-setup/index.md index e6a83cb5..6bb35b9a 100644 --- a/docs/sources/manage/mobile-app/installation-and-setup/index.md +++ b/docs/sources/manage/mobile-app/installation-and-setup/index.md @@ -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. diff --git a/grafana-plugin/src/containers/MobileAppConnection/MobileAppConnection.module.scss b/grafana-plugin/src/containers/MobileAppConnection/MobileAppConnection.module.scss index e63a980e..343f11f4 100644 --- a/grafana-plugin/src/containers/MobileAppConnection/MobileAppConnection.module.scss +++ b/grafana-plugin/src/containers/MobileAppConnection/MobileAppConnection.module.scss @@ -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; diff --git a/grafana-plugin/src/containers/MobileAppConnection/MobileAppConnection.tsx b/grafana-plugin/src/containers/MobileAppConnection/MobileAppConnection.tsx index a4284b8a..a6e61f47 100644 --- a/grafana-plugin/src/containers/MobileAppConnection/MobileAppConnection.tsx +++ b/grafana-plugin/src/containers/MobileAppConnection/MobileAppConnection.tsx @@ -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 = ; @@ -165,12 +167,10 @@ export const MobileAppConnection = observer(({ userPk }: Props) => { ); } else if (QRCodeValue) { - const QRCodeDataParsed = getParsedQRCodeValue(); - content = ( - Sign In + Sign in via QR Code 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) => { + {QRCodeDataParsed && isMobile && ( + + + + )} {content} diff --git a/grafana-plugin/src/containers/MobileAppConnection/parts/LinkLoginButton/LinkLoginButton.module.scss b/grafana-plugin/src/containers/MobileAppConnection/parts/LinkLoginButton/LinkLoginButton.module.scss new file mode 100644 index 00000000..e69de29b diff --git a/grafana-plugin/src/containers/MobileAppConnection/parts/LinkLoginButton/LinkLoginButton.test.tsx b/grafana-plugin/src/containers/MobileAppConnection/parts/LinkLoginButton/LinkLoginButton.test.tsx new file mode 100644 index 00000000..03c81eef --- /dev/null +++ b/grafana-plugin/src/containers/MobileAppConnection/parts/LinkLoginButton/LinkLoginButton.test.tsx @@ -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(); + expect(component.container).toMatchSnapshot(); + }); +}); diff --git a/grafana-plugin/src/containers/MobileAppConnection/parts/LinkLoginButton/LinkLoginButton.tsx b/grafana-plugin/src/containers/MobileAppConnection/parts/LinkLoginButton/LinkLoginButton.tsx new file mode 100644 index 00000000..54f97245 --- /dev/null +++ b/grafana-plugin/src/containers/MobileAppConnection/parts/LinkLoginButton/LinkLoginButton.tsx @@ -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) => { + const { baseUrl, token } = props; + const mobileDeepLink = `grafana://mobile/login/link-login?oncall_api_url=${baseUrl}&token=${token}`; + + return ( + + + Sign in via deeplink + + Make sure to have the app installed + + + ); +}; diff --git a/grafana-plugin/src/containers/MobileAppConnection/parts/LinkLoginButton/__snapshots__/LinkLoginButton.test.tsx.snap b/grafana-plugin/src/containers/MobileAppConnection/parts/LinkLoginButton/__snapshots__/LinkLoginButton.test.tsx.snap new file mode 100644 index 00000000..f68e7860 --- /dev/null +++ b/grafana-plugin/src/containers/MobileAppConnection/parts/LinkLoginButton/__snapshots__/LinkLoginButton.test.tsx.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LinkLoginButton it renders properly 1`] = ` +
+
+
+ + Sign in via deeplink + +
+
+ + Make sure to have the app installed + +
+
+ +
+
+
+`; diff --git a/grafana-plugin/src/utils/utils.ts b/grafana-plugin/src/utils/utils.ts index 2cad629d..99846408 100644 --- a/grafana-plugin/src/utils/utils.ts +++ b/grafana-plugin/src/utils/utils.ts @@ -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;