From 65262a78e0edf94bf866a076bb7f52cc4a79605f Mon Sep 17 00:00:00 2001 From: Kayub Maharjan Date: Fri, 11 Aug 2023 11:58:01 +1000 Subject: [PATCH 01/15] - Back button changed to cross - InstallationRequested split to a new route - Minor cleanup --- spa/src/app.tsx | 2 + spa/src/common/LoggedinInfo.tsx | 30 ++++++ spa/src/common/Wrapper.tsx | 5 +- spa/src/pages/ConfigSteps/index.tsx | 93 ++++++++----------- spa/src/pages/InstallationRequested/index.tsx | 48 ++++++++++ 5 files changed, 122 insertions(+), 56 deletions(-) create mode 100644 spa/src/common/LoggedinInfo.tsx create mode 100644 spa/src/pages/InstallationRequested/index.tsx diff --git a/spa/src/app.tsx b/spa/src/app.tsx index b808e8da96..6314647ee9 100644 --- a/spa/src/app.tsx +++ b/spa/src/app.tsx @@ -7,6 +7,7 @@ import { import StartConnection from "./pages/StartConnection"; import ConfigSteps from "./pages/ConfigSteps"; import Connected from "./pages/Connected"; +import InstallationRequested from "./pages/InstallationRequested"; import * as Sentry from "@sentry/react"; import { initSentry } from "./sentry"; @@ -32,6 +33,7 @@ const App = () => { }/> }/> }/> + }/> diff --git a/spa/src/common/LoggedinInfo.tsx b/spa/src/common/LoggedinInfo.tsx new file mode 100644 index 0000000000..e0eafacb54 --- /dev/null +++ b/spa/src/common/LoggedinInfo.tsx @@ -0,0 +1,30 @@ +import Button from "@atlaskit/button"; +import styled from "@emotion/styled"; +import { popup } from "../utils"; +import analyticsClient from "../analytics"; +import OAuthManager from "../services/oauth-manager"; + + +export const LoggedInContent = styled.div` + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto; +`; + +export const LoggedinInfo = ({ username, logout }: { username: string; logout: () => void;}) => { + const clicked = () => { + // Opens the popup for logging out of GitHub + popup("https://github.com/logout"); + // Clearing the locally stored tokens + OAuthManager.clear(); + // Passed callbacks, for re-rendering/changing states + logout(); + analyticsClient.sendUIEvent({ actionSubject: "switchGitHubAccount", action: "clicked" }); + }; + + return +
Logged in as {username}
+ +
; +}; diff --git a/spa/src/common/Wrapper.tsx b/spa/src/common/Wrapper.tsx index 61a3d5130b..215af47271 100644 --- a/spa/src/common/Wrapper.tsx +++ b/spa/src/common/Wrapper.tsx @@ -1,7 +1,7 @@ import { ReactNode } from "react"; import styled from "@emotion/styled"; import Button from "@atlaskit/button"; -import ArrowLeftIcon from "@atlaskit/icon/glyph/arrow-left"; +import CrossIcon from "@atlaskit/icon/glyph/cross"; import analyticsClient from "../analytics"; const navHeight = 56; @@ -27,7 +27,8 @@ export const Wrapper = (attr: { }) => { return - + } diff --git a/spa/src/pages/InstallationRequested/index.tsx b/spa/src/pages/InstallationRequested/index.tsx new file mode 100644 index 0000000000..601a49d78b --- /dev/null +++ b/spa/src/pages/InstallationRequested/index.tsx @@ -0,0 +1,48 @@ +import { LoggedinInfo } from "../../common/LoggedinInfo"; +import { Wrapper } from "../../common/Wrapper"; +import Step from "../../components/Step"; +import SyncHeader from "../../components/SyncHeader"; +import OAuthManager from "../../services/oauth-manager"; +import styled from "@emotion/styled"; +import { token } from "@atlaskit/tokens"; +import Button from "@atlaskit/button"; +import { useNavigate } from "react-router-dom"; + +const Paragraph = styled.div` + color: ${token("color.text.subtle")}; + margin-bottom: ${token("space.100")}; +`; + +const InstallationRequested = () => { + const navigate = useNavigate(); + const username = OAuthManager.getUserDetails().username || ""; + + const navigateBackToSteps = () => navigate("/spa/steps"); + + return ( + + + + <> + + Once the owner of this organization has installed Jira, you (or
+ another Jira admin) can come back here and finish the set up. +
+ + +
+ +
+ ); +}; + +export default InstallationRequested; From aff2cc8a94ecf0ceeccb910584190f2f9990c972 Mon Sep 17 00:00:00 2001 From: Kayub Maharjan Date: Fri, 11 Aug 2023 11:59:31 +1000 Subject: [PATCH 02/15] - Lint fixes --- spa/src/pages/ConfigSteps/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/spa/src/pages/ConfigSteps/index.tsx b/spa/src/pages/ConfigSteps/index.tsx index 91d1bd1879..2ff87d8505 100644 --- a/spa/src/pages/ConfigSteps/index.tsx +++ b/spa/src/pages/ConfigSteps/index.tsx @@ -257,6 +257,7 @@ const ConfigSteps = () => { return () => { window.removeEventListener("message", handler); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ originalUrl ]); useEffect(() => { From fb37241221a34d4cc3e14850693077bf2aaf51af Mon Sep 17 00:00:00 2001 From: Kayub Maharjan Date: Fri, 11 Aug 2023 12:23:22 +1000 Subject: [PATCH 03/15] - test case added for the request installation screen --- spa/src/pages/InstallationRequested/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/src/pages/InstallationRequested/index.tsx b/spa/src/pages/InstallationRequested/index.tsx index 601a49d78b..14828f2042 100644 --- a/spa/src/pages/InstallationRequested/index.tsx +++ b/spa/src/pages/InstallationRequested/index.tsx @@ -25,7 +25,7 @@ const InstallationRequested = () => { <> - Once the owner of this organization has installed Jira, you (or
+ Once the owner of this organization has installed Jira, you (or
another Jira admin) can come back here and finish the set up.
+
What's next? diff --git a/spa/src/pages/Connected/test.tsx b/spa/src/pages/Connected/test.tsx index 65a9d40e96..f5a7f57acb 100644 --- a/spa/src/pages/Connected/test.tsx +++ b/spa/src/pages/Connected/test.tsx @@ -1,5 +1,5 @@ import { BrowserRouter } from "react-router-dom"; -import { render, screen } from "@testing-library/react"; +import { act, render, screen } from "@testing-library/react"; import Connected from "./index"; import userEvent from "@testing-library/user-event"; @@ -10,6 +10,11 @@ import userEvent from "@testing-library/user-event"; go: jest.fn() } }; +const navigate = jest.fn(); +jest.mock("react-router-dom", () => ({ + ...jest.requireActual("react-router-dom"), + useNavigate: () => navigate +})); test("Basic check for the Connected Page", async () => { render( @@ -25,10 +30,15 @@ test("Basic check for the Connected Page", async () => { expect(screen.queryByText("Learn about issue linking")).toBeInTheDocument(); expect(screen.queryByText("Learn about development work in Jira")).toBeInTheDocument(); expect(screen.queryByText("Check your backfill status")).toBeInTheDocument(); + expect(screen.queryByText("Add another organization")).toBeInTheDocument(); expect(screen.getByText("Learn about issue linking")).toHaveAttribute("href", "https://support.atlassian.com/jira-software-cloud/docs/reference-issues-in-your-development-work/"); expect(screen.getByText("Learn about development work in Jira")).toHaveAttribute("href", "https://support.atlassian.com/jira-cloud-administration/docs/integrate-with-development-tools/"); await userEvent.click(screen.getByText("Check your backfill status")); expect(AP.navigator.go).toHaveBeenCalled(); + + await act(() => userEvent.click(screen.getByText("Add another organization"))); + expect(navigate).toHaveBeenCalledWith("/spa/steps"); + }); From 06b50a6ee5604c10b5b0ccce3e2243890cf64fcc Mon Sep 17 00:00:00 2001 From: Kayub Maharjan Date: Fri, 11 Aug 2023 12:43:42 +1000 Subject: [PATCH 07/15] - Removed the auto-redirect from the jira-get.ts --- src/routes/jira/jira-get.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/routes/jira/jira-get.ts b/src/routes/jira/jira-get.ts index ce77d562e8..8afe6e4c43 100644 --- a/src/routes/jira/jira-get.ts +++ b/src/routes/jira/jira-get.ts @@ -162,10 +162,6 @@ const renderJiraCloudAndEnterpriseServer = async (res: Response, req: Request): const hasConnections = !!(installations.total || gheServers?.length); const useNewSPAExperience = await booleanFlag(BooleanFlags.USE_NEW_5KU_SPA_EXPERIENCE, jiraHost); - if (useNewSPAExperience && !hasConnections) { - res.redirect("/spa?from=homepage"); - return; - } res.render("jira-configuration.hbs", { host: jiraHost, From 706a9465ee2096c7b9c4318fd8136391ec9de7af Mon Sep 17 00:00:00 2001 From: Kayub Maharjan Date: Fri, 11 Aug 2023 13:06:30 +1000 Subject: [PATCH 08/15] - Orgs list separated to a different component --- .../pages/ConfigSteps/OrgsContainer/index.tsx | 67 +++++++++++++++++++ spa/src/pages/ConfigSteps/index.tsx | 48 ++----------- 2 files changed, 73 insertions(+), 42 deletions(-) create mode 100644 spa/src/pages/ConfigSteps/OrgsContainer/index.tsx diff --git a/spa/src/pages/ConfigSteps/OrgsContainer/index.tsx b/spa/src/pages/ConfigSteps/OrgsContainer/index.tsx new file mode 100644 index 0000000000..1130e5fe9c --- /dev/null +++ b/spa/src/pages/ConfigSteps/OrgsContainer/index.tsx @@ -0,0 +1,67 @@ +import Button, { LoadingButton } from "@atlaskit/button"; +import { GitHubInstallationType } from "../../../../../src/rest-interfaces"; +import styled from "@emotion/styled"; +import { token } from "@atlaskit/tokens"; +import { useState } from "react"; + +const OrgsWrapper = styled.div` + max-height: 250px; + overflow-y: auto; + padding-right: 80px; + margin-right: -80px; +`; +const OrgDiv = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + padding: ${token("space.150")} 0; + margin-bottom: ${token("space.100")}; +`; + +const OrganizationsList = ({ + organizations, + loaderForOrgClicked, + setLoaderForOrgClicked, + connectingOrg, +}: { + organizations: Array; + // Passing down the states and methods from the parent component + loaderForOrgClicked: boolean; + setLoaderForOrgClicked: (args: boolean) => void; + connectingOrg: (org: GitHubInstallationType) => void; +}) => { + const [clickedOrg, setClickedOrg] = useState(undefined); + + return ( + + { + organizations.map(org => + + {org.account.login} + { + loaderForOrgClicked && clickedOrg?.id === org.id ? + Loading button : + + } + + ) + } + + ); +}; + +export default OrganizationsList; diff --git a/spa/src/pages/ConfigSteps/index.tsx b/spa/src/pages/ConfigSteps/index.tsx index 2ff87d8505..54ebc030da 100644 --- a/spa/src/pages/ConfigSteps/index.tsx +++ b/spa/src/pages/ConfigSteps/index.tsx @@ -19,6 +19,7 @@ import { AxiosError } from "axios"; import { ErrorObjType, modifyError } from "../../utils/modifyError"; import { reportError } from "../../utils"; import { GitHubInstallationType } from "../../../../src/rest-interfaces"; +import OrganizationsList from "../ConfigSteps/OrgsContainer"; type GitHubOptionType = { selectedOption: number; @@ -91,19 +92,6 @@ const AddOrganizationContainer = styled.div` } } `; -const OrgsContainer = styled.div` - max-height: 250px; - overflow-y: auto; - padding-right: 80px; - margin-right: -80px; -`; -const OrgDiv = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - padding: ${token("space.150")} 0; - margin-bottom: ${token("space.100")}; -`; const Paragraph = styled.div` color: ${token("color.text.subtle")}; margin-bottom: ${token("space.100")}; @@ -133,8 +121,6 @@ const ConfigSteps = () => { const [isLoggedIn, setIsLoggedIn] = useState(isAuthenticated); const [loggedInUser, setLoggedInUser] = useState(username); const [loaderForLogin, setLoaderForLogin] = useState(false); - - const [clickedOrg, setClickedOrg] = useState(0); const [loaderForOrgClicked, setLoaderForOrgClicked] = useState(false); const [error, setError] = useState(undefined); @@ -330,33 +316,11 @@ const ConfigSteps = () => { organizations.length === 0 && No organizations found! } - - { - organizations.map(org => - - {org.account.login} - { - loaderForOrgClicked && clickedOrg === org.id ? - Loading button : - - } - - ) - } - + doCreateConnection(org.id, "manual", org.account?.login)} /> ; }; + +export default LoggedinInfo; diff --git a/spa/src/pages/ConfigSteps/SkeletonForLoading/index.tsx b/spa/src/pages/ConfigSteps/SkeletonForLoading/index.tsx new file mode 100644 index 0000000000..a9069b427a --- /dev/null +++ b/spa/src/pages/ConfigSteps/SkeletonForLoading/index.tsx @@ -0,0 +1,38 @@ +import Step from "../../../components/Step"; +import Skeleton from "@atlaskit/skeleton"; +import styled from "@emotion/styled"; + +const Content = styled.div` + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto; +`; + +const SkeletonForLoading = () => <> + } + > + + + + + +; + +export default SkeletonForLoading; diff --git a/spa/src/pages/ConfigSteps/index.tsx b/spa/src/pages/ConfigSteps/index.tsx index 54ebc030da..62af8f3809 100644 --- a/spa/src/pages/ConfigSteps/index.tsx +++ b/spa/src/pages/ConfigSteps/index.tsx @@ -5,9 +5,8 @@ import styled from "@emotion/styled"; import SyncHeader from "../../components/SyncHeader"; import { Wrapper } from "../../common/Wrapper"; import Step from "../../components/Step"; -import { LoggedInContent, LoggedinInfo } from "../../common/LoggedinInfo"; +import LoggedinInfo from "../../common/LoggedinInfo"; import Tooltip, { TooltipPrimitive } from "@atlaskit/tooltip"; -import Skeleton from "@atlaskit/skeleton"; import { token } from "@atlaskit/tokens"; import OpenIcon from "@atlaskit/icon/glyph/open"; import { useNavigate } from "react-router-dom"; @@ -20,6 +19,7 @@ import { ErrorObjType, modifyError } from "../../utils/modifyError"; import { reportError } from "../../utils"; import { GitHubInstallationType } from "../../../../src/rest-interfaces"; import OrganizationsList from "../ConfigSteps/OrgsContainer"; +import SkeletonForLoading from "../ConfigSteps/SkeletonForLoading"; type GitHubOptionType = { selectedOption: number; @@ -269,32 +269,6 @@ const ConfigSteps = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [ isLoggedIn ]); - const SkeletonForLoading = () => <> - } - > - - - - - - ; - return ( @@ -390,8 +364,6 @@ const ConfigSteps = () => { } - - ); diff --git a/spa/src/pages/InstallationRequested/index.tsx b/spa/src/pages/InstallationRequested/index.tsx index 14828f2042..e8987e5fc0 100644 --- a/spa/src/pages/InstallationRequested/index.tsx +++ b/spa/src/pages/InstallationRequested/index.tsx @@ -1,4 +1,4 @@ -import { LoggedinInfo } from "../../common/LoggedinInfo"; +import LoggedinInfo from "../../common/LoggedinInfo"; import { Wrapper } from "../../common/Wrapper"; import Step from "../../components/Step"; import SyncHeader from "../../components/SyncHeader"; From e4dcba9152371c3ad436163db8951b94bece66b8 Mon Sep 17 00:00:00 2001 From: Kayub Maharjan Date: Fri, 11 Aug 2023 13:37:58 +1000 Subject: [PATCH 10/15] - WIP - error states --- .../pages/ConfigSteps/OrgsContainer/index.tsx | 52 ++++++++++++------- spa/src/pages/ConfigSteps/index.tsx | 13 +++-- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/spa/src/pages/ConfigSteps/OrgsContainer/index.tsx b/spa/src/pages/ConfigSteps/OrgsContainer/index.tsx index 1130e5fe9c..2c55156cc3 100644 --- a/spa/src/pages/ConfigSteps/OrgsContainer/index.tsx +++ b/spa/src/pages/ConfigSteps/OrgsContainer/index.tsx @@ -3,6 +3,7 @@ import { GitHubInstallationType } from "../../../../../src/rest-interfaces"; import styled from "@emotion/styled"; import { token } from "@atlaskit/tokens"; import { useState } from "react"; +import WarningIcon from "@atlaskit/icon/glyph/warning"; const OrgsWrapper = styled.div` max-height: 250px; @@ -31,31 +32,46 @@ const OrganizationsList = ({ connectingOrg: (org: GitHubInstallationType) => void; }) => { const [clickedOrg, setClickedOrg] = useState(undefined); + const canConnect = (org: GitHubInstallationType) => !org.requiresSsoLogin && !org.isIPBlocked && org.isAdmin; + + const errorMessage = (org: GitHubInstallationType) => { + return "THis is the message " + org.account.login; + }; return ( { organizations.map(org => - {org.account.login} { - loaderForOrgClicked && clickedOrg?.id === org.id ? - Loading button : - + canConnect(org) ? <> + {org.account.login} + { + loaderForOrgClicked && clickedOrg?.id === org.id ? + Loading button : + + } + : <> +
+ {org.account.login} +
{errorMessage(org)}
+
+ + }
) diff --git a/spa/src/pages/ConfigSteps/index.tsx b/spa/src/pages/ConfigSteps/index.tsx index 62af8f3809..95f1e59fcd 100644 --- a/spa/src/pages/ConfigSteps/index.tsx +++ b/spa/src/pages/ConfigSteps/index.tsx @@ -287,14 +287,13 @@ const ConfigSteps = () => { projects in {hostUrl?.jiraHost}. { - organizations.length === 0 && - No organizations found! + organizations.length === 0 ? No organizations found! : + doCreateConnection(org.id, "manual", org.account?.login)} /> } - doCreateConnection(org.id, "manual", org.account?.login)} /> + ; + } + + if (!org.isAdmin) { + return <> + + Can't connect, you're not an organization owner.
Ask an owner to complete this step. +
+ ; + } }; return ( @@ -45,7 +96,7 @@ const OrganizationsList = ({ { canConnect(org) ? <> - {org.account.login} + {org.account.login} { loaderForOrgClicked && clickedOrg?.id === org.id ? Loading button : @@ -67,10 +118,12 @@ const OrganizationsList = ({ } : <>
- {org.account.login} + {org.account.login}
{errorMessage(org)}
- + + + }
diff --git a/spa/src/pages/ConfigSteps/index.tsx b/spa/src/pages/ConfigSteps/index.tsx index 95f1e59fcd..372ca0f0f9 100644 --- a/spa/src/pages/ConfigSteps/index.tsx +++ b/spa/src/pages/ConfigSteps/index.tsx @@ -8,7 +8,6 @@ import Step from "../../components/Step"; import LoggedinInfo from "../../common/LoggedinInfo"; import Tooltip, { TooltipPrimitive } from "@atlaskit/tooltip"; import { token } from "@atlaskit/tokens"; -import OpenIcon from "@atlaskit/icon/glyph/open"; import { useNavigate } from "react-router-dom"; import Error from "../../components/Error"; import AppManager from "../../services/app-manager"; @@ -280,11 +279,11 @@ const ConfigSteps = () => { isLoggedIn ? <> { loaderForOrgFetching ? : <> - + <> - Repositories from this organization will be available to all
- projects in {hostUrl?.jiraHost}. + This organization's repositories will be available to all projects
+ in {hostUrl?.jiraHost}.
{ organizations.length === 0 ? No organizations found! : @@ -292,6 +291,7 @@ const ConfigSteps = () => { organizations={organizations} loaderForOrgClicked={loaderForOrgClicked} setLoaderForOrgClicked={setLoaderForOrgClicked} + clearGitHubToken={clearGitHubToken} connectingOrg={(org) => doCreateConnection(org.id, "manual", org.account?.login)} /> } @@ -302,7 +302,7 @@ const ConfigSteps = () => { onClick={() => installNewOrg("manual")} />
!loaderForOrgClicked && installNewOrg("manual")}> - Add an organization + { organizations.length === 0 ? "Select an organization in GitHub" : "Select another organization" }
@@ -352,12 +352,11 @@ const ConfigSteps = () => { { loaderForLogin ? Loading : } diff --git a/spa/src/pages/Connected/index.tsx b/spa/src/pages/Connected/index.tsx index a3a1e3acc8..00ae6d71d2 100644 --- a/spa/src/pages/Connected/index.tsx +++ b/spa/src/pages/Connected/index.tsx @@ -61,15 +61,14 @@ const Connected = () => { GitHub is connected! - Its' time to let everyone know that GitHub's ready to use in their
- project. For development work to appear in Jira, your team
- needs to link their work using issue keys.
+ Its' time to let everyone know that GitHub's ready to use and your
+ team can use issue keys to link work to Jira.
@@ -111,10 +110,16 @@ const Connected = () => {
- We're backfilling your organization's repositories into Jira (this
+ We're backfilling your organization's repositories into Jira (this
can take a while, depending on how many repositories you
- have).
- + have). +
); diff --git a/spa/src/utils/modifyError.tsx b/spa/src/utils/modifyError.tsx index 6259b691fa..11fcb2effc 100644 --- a/spa/src/utils/modifyError.tsx +++ b/spa/src/utils/modifyError.tsx @@ -18,7 +18,7 @@ type ErrorWithErrorCode = { errorCode: ErrorCode }; -const GENERIC_MESSAGE = "Something went wrong, please try again later."; +const GENERIC_MESSAGE = "Something went wrong and we couldn’t connect to GitHub, try again."; export const modifyError = ( error: AxiosError | SimpleError | ErrorWithErrorCode, From dfa14c65a28c55d78aebda2a361510a8a579fe47 Mon Sep 17 00:00:00 2001 From: Kayub Maharjan Date: Fri, 11 Aug 2023 15:18:02 +1000 Subject: [PATCH 12/15] - Dropdown component removed - Text changes and updating test cases --- spa/src/components/SelectDropdown/index.tsx | 72 --------------------- spa/src/components/SelectDropdown/test.tsx | 28 -------- spa/src/pages/ConfigSteps/index.tsx | 6 +- spa/src/pages/ConfigSteps/test.tsx | 61 +++++++++-------- 4 files changed, 38 insertions(+), 129 deletions(-) delete mode 100644 spa/src/components/SelectDropdown/index.tsx delete mode 100644 spa/src/components/SelectDropdown/test.tsx diff --git a/spa/src/components/SelectDropdown/index.tsx b/spa/src/components/SelectDropdown/index.tsx deleted file mode 100644 index 250cba7d67..0000000000 --- a/spa/src/components/SelectDropdown/index.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { Label } from "@atlaskit/form"; -import Select from "@atlaskit/select"; -import styled from "@emotion/styled"; -import { token } from "@atlaskit/tokens"; -import React from "react"; - -export type OrgOptionsType = { - label?: string; - options: Array -}; - -export type LabelType = { - label: string; - value: string; - requiresSsoLogin?: boolean; - isIPBlocked?: boolean; - isAdmin?: boolean; -}; - -const SelectDropdownContainer = styled.div` - margin: ${token("space.150")} 0; - position: relative; -`; -const IconContainer = styled.span` - position: absolute; - z-index: 2; - top: 30px; - left: 8px; -`; -const SelectContainer = styled.div` - .react-select__value-container { - padding-left: ${token("space.400")}; - } -`; - -const SelectDropdown = ({ - options, - label, - onChange, - placeholder = "", - isLoading = false, - icon, -}: { - options: Array, - label: string, - onChange: (args: LabelType | null) => void, - placeholder?: string, - isLoading: boolean, - icon?: React.JSX.Element -}) => { - return (<> - - - {icon} - - - -