Skip to content

Commit

Permalink
5KU error messages mapping (#2360)
Browse files Browse the repository at this point in the history
- Error messages mapped for any action
- Cleanup & optimization
  • Loading branch information
krazziekay authored Aug 22, 2023
1 parent 2353d6e commit fd75cb8
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 90 deletions.
48 changes: 48 additions & 0 deletions spa/src/components/Error/KnownErrors/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import styled from "@emotion/styled";
import { token } from "@atlaskit/tokens";
import { popup } from "../../../utils";
import Button from "@atlaskit/button";

const Paragraph = styled.div`
color: ${token("color.text.subtle")};
`;
const BulletSeparator = styled.span`
padding: 0 ${token("space.100")};
`;
const StyledLink = styled.a`
cursor: pointer;
`;

/************************************************************************
* UI view for the 3 known errors
************************************************************************/
export const ErrorForSSO = ({ orgName, accessUrl, resetCallback }: { orgName?: string; accessUrl: string; resetCallback: () => void;}) => <>
<Paragraph>
Can't connect, single sign-on(SSO) required{orgName && <span> for <b>{orgName}</b></span>}.
</Paragraph>
<Paragraph>
1. <StyledLink onClick={() => popup(accessUrl)}>Log into GitHub with SSO</StyledLink>.
</Paragraph>
<Paragraph>
2. <StyledLink onClick={resetCallback}>Retry connection in Jira</StyledLink> (once logged in).
</Paragraph>
</>;

export const ErrorForNonAdmins = ({ orgName }: { orgName?: string; }) => <Paragraph>
Can't connect, you're not the organization owner{orgName && <span> of <b>{orgName}</b></span>}.<br />Ask an owner to complete this step.
</Paragraph>;

export const ErrorForIPBlocked = ({ orgName, resetCallback }: { orgName?: string; resetCallback: () => void }) => <>
<Paragraph>
Can't connect{orgName && <span> to <b>{orgName}</b></span>}, blocked by your IP allow list.
</Paragraph>
<Button
style={{ paddingLeft: 0, paddingRight: 0 }}
appearance="link"
onClick={() => popup("https://github.com/atlassian/github-for-jira/blob/main/docs/ip-allowlist.md")}
>
How to update allowlist
</Button>
<BulletSeparator>&#8226;</BulletSeparator>
<StyledLink onClick={resetCallback}>Retry</StyledLink>
</>;
8 changes: 4 additions & 4 deletions spa/src/components/Error/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ const ErrorWrapper = styled.div<ErrorWrapperType>`
background: ${props => props.type === "warning" ? token("color.background.warning") : token("color.background.danger") };
border-radius: 3px;
align-items: center;
div {
padding-left: ${token("space.200")};
}
span {
align-self: start;
}
`;
const ErrorContent = styled.div`
padding-left: ${token("space.200")};
`;

const Error = ({
type,
Expand All @@ -42,7 +42,7 @@ const Error = ({
type === "warning" ? <WarningIcon label="warning" primaryColor={token("color.background.warning.bold")} size="medium" /> :
<ErrorIcon label="warning" primaryColor={token("color.background.danger.bold")} size="medium" />
}
<div>{message}</div>
<ErrorContent>{message}</ErrorContent>
</ErrorWrapper>
);
};
Expand Down
45 changes: 5 additions & 40 deletions spa/src/pages/ConfigSteps/OrgsContainer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import styled from "@emotion/styled";
import { token } from "@atlaskit/tokens";
import { useState } from "react";
import WarningIcon from "@atlaskit/icon/glyph/warning";
import { popup } from "../../../utils";
import OauthManager from "../../../services/oauth-manager";
import { ErrorForIPBlocked, ErrorForNonAdmins, ErrorForSSO } from "../../../components/Error/KnownErrors";

type OrgDivType = {
key: number;
Expand All @@ -29,18 +29,10 @@ const OrgName = styled.span`
color: ${token("color.text")};
font-weight: 590;
`;
const Paragraph = styled.div`
color: ${token("color.text.subtle")};
`;
const IconWrapper = styled.div`
padding-top: ${token("space.150")};
`;
const BulletSeparator = styled.span`
padding: 0 ${token("space.100")};
`;
const StyledLink = styled.a`
cursor: pointer;
`;


const OrganizationsList = ({
organizations,
Expand Down Expand Up @@ -73,42 +65,15 @@ const OrganizationsList = ({
// TODO: Update this to support GHE
const accessUrl = `https://github.com/organizations/${org.account.login}/settings/profile`;

return <>
<Paragraph>
Can't connect, single sign-on(SSO) required.
</Paragraph>
<Paragraph>
1. <StyledLink onClick={() => popup(accessUrl)}>Log into GitHub with SSO</StyledLink>.
</Paragraph>
<Paragraph>
2. <StyledLink onClick={resetToken}>Retry connection in Jira</StyledLink> (once logged in).
</Paragraph>
</>;
return <ErrorForSSO resetCallback={resetToken} accessUrl={accessUrl} />;
}

if (org.isIPBlocked) {
return <>
<Paragraph>
Can't connect, blocked by your IP allow list.
</Paragraph>
<Button
style={{ paddingLeft: 0, paddingRight: 0 }}
appearance="link"
onClick={() => popup("https://github.com/atlassian/github-for-jira/blob/main/docs/ip-allowlist.md")}
>
How to update allowlist
</Button>
<BulletSeparator>&#8226;</BulletSeparator>
<StyledLink onClick={resetToken}>Retry</StyledLink>
</>;
return <ErrorForIPBlocked resetCallback={resetToken} />;
}

if (!org.isAdmin) {
return <>
<Paragraph>
Can't connect, you're not the organization owner.<br />Ask an owner to complete this step.
</Paragraph>
</>;
return <ErrorForNonAdmins />;
}
};

Expand Down
23 changes: 17 additions & 6 deletions spa/src/pages/ConfigSteps/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { reportError } from "../../utils";
import { GitHubInstallationType } from "../../../../src/rest-interfaces";
import OrganizationsList from "../ConfigSteps/OrgsContainer";
import SkeletonForLoading from "../ConfigSteps/SkeletonForLoading";
import OauthManager from "../../services/oauth-manager";

type GitHubOptionType = {
selectedOption: number;
Expand Down Expand Up @@ -161,7 +162,7 @@ const ConfigSteps = () => {
const response = await AppManager.fetchOrgs();
setLoaderForOrgFetching(false);
if (response instanceof AxiosError) {
showError(modifyError(response, {}, { onClearGitHubToken: clearGitHubToken }));
showError(modifyError(response, {}, { onClearGitHubToken: clearGitHubToken, onRelogin: reLogin }));
return { success: false, orgs: [] };
} else {
setOrganizations(response.orgs);
Expand All @@ -180,7 +181,7 @@ const ConfigSteps = () => {
});
} catch (e) {
setLoaderForLogin(false);
showError(modifyError(e as AxiosError, {}, { onClearGitHubToken: clearGitHubToken }));
showError(modifyError(e as AxiosError, {}, { onClearGitHubToken: clearGitHubToken, onRelogin: reLogin }));
reportError(e);
}
break;
Expand All @@ -199,6 +200,16 @@ const ConfigSteps = () => {
clearLogin();
};

const reLogin = async () => {
// Clearing the errors
showError(undefined);
await OauthManager.clear();
// This resets the token validity check in the parent component and resets the UI
setIsLoggedIn(false);
// Restart the whole auth flow
await OauthManager.authenticateInGitHub(() => {});
};

const clearLogin = () => {
setIsLoggedIn(false);
setLoaderForLogin(false);
Expand All @@ -212,7 +223,7 @@ const ConfigSteps = () => {
const connected = await AppManager.connectOrg(gitHubInstallationId);
analyticsClient.sendTrackEvent({ actionSubject: "organisationConnectResponse", action: connected ? "success" : "fail"}, { mode });
if (connected instanceof AxiosError) {
showError(modifyError(connected, { orgLogin }, { onClearGitHubToken: clearGitHubToken }));
showError(modifyError(connected, { orgLogin }, { onClearGitHubToken: clearGitHubToken, onRelogin: reLogin }));
} else {
navigate("/spa/connected");
}
Expand All @@ -238,7 +249,7 @@ const ConfigSteps = () => {
}
});
} catch (e) {
showError(modifyError(e as AxiosError, { }, { onClearGitHubToken: clearGitHubToken }));
showError(modifyError(e as AxiosError, { }, { onClearGitHubToken: clearGitHubToken, onRelogin: reLogin }));
analyticsClient.sendTrackEvent({ actionSubject: "installNewOrgInGithubResponse", action: "fail"}, { mode });
reportError(e);
}
Expand All @@ -252,7 +263,7 @@ const ConfigSteps = () => {
const response = await OAuthManager.finishOAuthFlow(event.data?.code, event.data?.state);
setLoaderForLogin(false);
if (response instanceof AxiosError) {
showError(modifyError(response, {}, { onClearGitHubToken: clearGitHubToken }));
showError(modifyError(response, {}, { onClearGitHubToken: clearGitHubToken, onRelogin: reLogin }));
analyticsClient.sendTrackEvent({ actionSubject: "finishOAuthFlow", action: "fail" });
return;
} else {
Expand All @@ -272,7 +283,7 @@ const ConfigSteps = () => {
const recheckValidity = async () => {
const status: boolean | AxiosError = await OAuthManager.checkValidity();
if (status instanceof AxiosError) {
showError(modifyError(status, {}, { onClearGitHubToken: clearGitHubToken }));
showError(modifyError(status, {}, { onClearGitHubToken: clearGitHubToken, onRelogin: reLogin }));
return;
}
setLoggedInUser(OAuthManager.getUserDetails().username);
Expand Down
58 changes: 18 additions & 40 deletions spa/src/utils/modifyError.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { AxiosError } from "axios";
import { ErrorType, ApiError, ErrorCode } from "rest-interfaces";
import React, { MouseEvent } from "react";
import Heading from "@atlaskit/heading";
import styled from "@emotion/styled";
import { token } from "@atlaskit/tokens";
import { ErrorForIPBlocked, ErrorForNonAdmins, ErrorForSSO } from "../components/Error/KnownErrors";

export type ErrorObjType = {
type: ErrorType,
Expand All @@ -28,12 +26,8 @@ export const GENERIC_MESSAGE_WITH_LINK = <>
export const modifyError = (
error: AxiosError<ApiError> | SimpleError | ErrorWithErrorCode,
context: { orgLogin?: string; },
callbacks: { onClearGitHubToken: (e: MouseEvent<HTMLAnchorElement>) => void }
callbacks: { onClearGitHubToken: (e: MouseEvent<HTMLAnchorElement>) => void; onRelogin: () => void }
): ErrorObjType => {

const Paragraph = styled.p`
color: ${token("color.text.subtle")};
`;
const errorObj = { type: "error" as ErrorType };
const warningObj = { type: "warning" as ErrorType };
let errorCode: ErrorCode = "UNKNOWN";
Expand All @@ -47,50 +41,34 @@ export const modifyError = (

// TODO: map all of the remaining backend errors in frontend
if (errorCode === "IP_BLOCKED") {
return {
...warningObj,
message: <ErrorForIPBlocked resetCallback={callbacks.onRelogin} orgName={context.orgLogin} />
};
} else if (errorCode === "SSO_LOGIN") {
// TODO: Update this to support GHE
const accessUrl = `https://github.com/organizations/${context.orgLogin}/settings/profile`;

return {
...warningObj,
message: <>
<Heading level="h500">GitHub for Jira is blocked by your IP allowlist</Heading>
<Paragraph>
Your GitHub organization only allows access to some IP addresses. To view<br />
development work in Jira, you need to add GitHub for Jira’s IP addresses to<br />
your allowlist.
</Paragraph>
<br />
<a target="_blank" href="https://github.com/atlassian/github-for-jira/blob/main/docs/ip-allowlist.md">Learn how to add GitHub for Jira to your IP allowlist</a>
<ErrorForSSO accessUrl={accessUrl} resetCallback={callbacks.onRelogin} orgName={context.orgLogin} />
</>
};
} else if (errorCode === "INSUFFICIENT_PERMISSION") {
return { ...errorObj, message: `You are not Admin of the target org ${context.orgLogin || ""}. Please make sure you are admin of the org and try again.` }; //TODO: Better message
} else if (errorCode === "TIMEOUT") {
return { ...errorObj, message: "Request timeout. Please try again later." }; //TODO: Better message
} else if (errorCode === "RATELIMIT") {
return { ...errorObj, message: "GitHub rate limit exceeded. Please try again later." }; //TODO: Better message
} else if (errorCode === "SSO_LOGIN") {
return {
...warningObj,
message: <>
<Heading level="h500">SSO Login required</Heading>
<Paragraph>
You cannot connect to this organization because you are not currently logged in through your SSO in GitHub.
<br></br>
Please follow the following steps:
<ol>
<li>
Please go to the <a target="_blank" href={`https://github.com/organizations/${context?.orgLogin}/settings/profile`}> organization settings</a> page and make sure you have admin access there.
</li>
<li>
Please click <a href="" onClick={callbacks.onClearGitHubToken}>this link</a> to reset your token. This will allow you to connect to this organization.
</li>
</ol>
</Paragraph>
</>
message: <ErrorForNonAdmins orgName={context.orgLogin} />
};
} else if (errorCode === "TIMEOUT") {
return { ...errorObj, message: "Request timeout. Please try again later." };
} else if (errorCode === "RATELIMIT") {
return { ...errorObj, message: "GitHub rate limit exceeded. Please try again later." };
} else if (errorCode === "INVALID_TOKEN") {
return {
...warningObj,
...errorObj,
message: <>
<span>"The GitHub token seems invalid, please <a href="" onClick={callbacks.onClearGitHubToken}>re-authorise</a> and try again."</span>
<span>GitHub token seems invalid, please <a href="" onClick={callbacks.onClearGitHubToken}>login again</a>.</span>
</>
};
} else {
Expand Down

0 comments on commit fd75cb8

Please sign in to comment.