Skip to content

Commit

Permalink
Add GitLab auth flow to application detail and remove (#1221)
Browse files Browse the repository at this point in the history
* GitLab auth flow works in app detail commits table alert

* gitlab auth works on remove app page

* removed unnecessary imports

* updated commits table tests with provider prop
  • Loading branch information
joshri authored Dec 15, 2021
1 parent 51ed317 commit 586e6f9
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 100 deletions.
13 changes: 10 additions & 3 deletions ui/components/CommitsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Props = {
app: Application;
authSuccess: boolean;
onAuthClick?: () => void;
provider: string;
};

const timestamp = (isoStr: string) => {
Expand All @@ -30,15 +31,21 @@ const timestamp = (isoStr: string) => {
return DateTime.fromISO(isoStr).toRelative();
};

function CommitsTable({ className, app, authSuccess, onAuthClick }: Props) {
function CommitsTable({
className,
app,
authSuccess,
onAuthClick,
provider,
}: Props) {
const [commits, loading, error, req] = useListCommits();

React.useEffect(() => {
if (!app || !app.name) {
return;
}

req(GitProvider.GitHub, {
req(GitProvider[provider], {
name: app.name,
namespace: app.namespace,
pageSize: 10,
Expand All @@ -48,7 +55,7 @@ function CommitsTable({ className, app, authSuccess, onAuthClick }: Props) {
if (error) {
return error.code === GrpcErrorCodes.Unauthenticated ? (
<AuthAlert
provider={GitProvider.GitHub}
provider={GitProvider[provider]}
title="Error fetching commits"
onClick={onAuthClick}
/>
Expand Down
20 changes: 14 additions & 6 deletions ui/components/__tests__/CommitsTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ describe("CommitsTable", () => {
await act(async () => {
const { container: div } = render(
withTheme(
withContext(<CommitsTable app={app} authSuccess={true} />, "/", {
applicationsClient: createMockClient(ovr),
})
withContext(
<CommitsTable app={app} authSuccess={true} provider={"GitHub"} />,
"/",
{
applicationsClient: createMockClient(ovr),
}
)
),
container
);
Expand All @@ -65,9 +69,13 @@ describe("CommitsTable", () => {
await act(async () => {
render(
withTheme(
withContext(<CommitsTable app={app} authSuccess={true} />, "/", {
applicationsClient: createMockClient(ovr),
})
withContext(
<CommitsTable app={app} authSuccess={true} provider={"GitHub"} />,
"/",
{
applicationsClient: createMockClient(ovr),
}
)
),
container
);
Expand Down
2 changes: 1 addition & 1 deletion ui/lib/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function getProviderToken(providerName: GitProvider): string {

const CALLBACK_STATE_KEY = "oauth_callback_state";

export type CallbackSessionState = { page: PageRoute; state: any };
export type CallbackSessionState = { page: PageRoute | string; state: any };

export function storeCallbackState(value: CallbackSessionState) {
if (!window.sessionStorage) {
Expand Down
132 changes: 76 additions & 56 deletions ui/pages/ApplicationDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Page from "../components/Page";
import ReconciliationGraph from "../components/ReconciliationGraph";
import Spacer from "../components/Spacer";
import { AppContext } from "../contexts/AppContext";
import CallbackStateContextProvider from "../contexts/CallbackStateContext";
import { useRequestState } from "../hooks/common";
import {
AutomationKind,
Expand All @@ -23,6 +24,7 @@ import {
} from "../lib/api/applications/applications.pb";
import { getChildren } from "../lib/graph";
import { PageRoute } from "../lib/types";
import { formatURL } from "../lib/utils";

type Props = {
className?: string;
Expand All @@ -34,14 +36,21 @@ function ApplicationDetail({ className, name }: Props) {
React.useContext(AppContext);
const [authSuccess, setAuthSuccess] = React.useState(false);
const [githubAuthModalOpen, setGithubAuthModalOpen] = React.useState(false);

const [reconciledObjects, setReconciledObjects] = React.useState<
UnstructuredObject[]
>([]);
const [provider, setProvider] = React.useState("");
const [res, loading, error, req] = useRequestState<GetApplicationResponse>();

const [syncRes, syncLoading, syncError, syncRequest] =
useRequestState<SyncApplicationResponse>();
const { getCallbackState, clearCallbackState } = React.useContext(AppContext);

const callbackState = getCallbackState();

if (callbackState) {
setAuthSuccess(true);
clearCallbackState();
}

React.useEffect(() => {
const p = async () => {
Expand All @@ -53,6 +62,7 @@ function ApplicationDetail({ className, name }: Props) {
const { provider } = await applicationsClient.ParseRepoURL({
url: res.application.url,
});
setProvider(provider);

return { ...res, provider };
};
Expand Down Expand Up @@ -99,14 +109,14 @@ function ApplicationDetail({ className, name }: Props) {

return (
<Page
loading={loading}
loading={loading ? true : false}
breadcrumbs={[{ page: PageRoute.Applications }]}
title={name}
className={className}
topRight={
<Flex align>
<Button
loading={syncLoading}
loading={syncLoading ? true : false}
onClick={() => {
syncRequest(
applicationsClient.SyncApplication({
Expand All @@ -130,60 +140,70 @@ function ApplicationDetail({ className, name }: Props) {
</Flex>
}
>
{syncError ? (
<Alert
severity="error"
title="Error syncing Application"
message={syncError.message}
<CallbackStateContextProvider
callbackState={{
page: formatURL(PageRoute.ApplicationDetail, { name }),
state: { authSuccess: false },
}}
>
{syncError ? (
<Alert
severity="error"
title="Error syncing Application"
message={syncError.message}
/>
) : (
authSuccess && (
<Alert severity="success" message="Authentication Successful" />
)
)}
<KeyValueTable
columns={4}
pairs={[
{ key: "Name", value: application.name },
{ key: "Deployment Type", value: application.deploymentType },
{ key: "URL", value: application.url },
{ key: "Path", value: application.path },
]}
/>
<ReconciliationGraph
objects={reconciledObjects}
parentObject={application}
parentObjectKind="Application"
/>
<h3>Source Conditions</h3>
<ConditionsTable conditions={application.source?.conditions} />
<h3>Automation Conditions</h3>
<ConditionsTable
conditions={
application.deploymentType == AutomationKind.Kustomize
? application.kustomization?.conditions
: application.helmRelease?.conditions
}
/>
) : (
authSuccess && (
<Alert severity="success" message="Authentication Successful" />
)
)}
<KeyValueTable
columns={4}
pairs={[
{ key: "Name", value: application.name },
{ key: "Deployment Type", value: application.deploymentType },
{ key: "URL", value: application.url },
{ key: "Path", value: application.path },
]}
/>
<ReconciliationGraph
objects={reconciledObjects}
parentObject={application}
parentObjectKind="Application"
/>
<h3>Source Conditions</h3>
<ConditionsTable conditions={application.source?.conditions} />
<h3>Automation Conditions</h3>
<ConditionsTable
conditions={
application.deploymentType == AutomationKind.Kustomize
? application.kustomization?.conditions
: application.helmRelease?.conditions
}
/>

<h3>Commits</h3>
<CommitsTable
// Get CommitsTable to retry after auth
app={application}
authSuccess={authSuccess}
onAuthClick={() => setGithubAuthModalOpen(true)}
/>
<GithubDeviceAuthModal
bodyClassName="auth-modal-size"
onSuccess={() => {
setAuthSuccess(true);
}}
repoName={application.url}
onClose={() => {
setGithubAuthModalOpen(false);
}}
open={githubAuthModalOpen}
/>
<h3>Commits</h3>
<CommitsTable
// Get CommitsTable to retry after auth
app={application}
authSuccess={authSuccess}
onAuthClick={() => {
if (provider === "Github") setGithubAuthModalOpen(true);
}}
provider={provider}
/>
<GithubDeviceAuthModal
bodyClassName="auth-modal-size"
onSuccess={() => {
setAuthSuccess(true);
}}
repoName={application.url}
onClose={() => {
setGithubAuthModalOpen(false);
}}
open={githubAuthModalOpen}
/>
</CallbackStateContextProvider>
</Page>
);
}
Expand Down
85 changes: 51 additions & 34 deletions ui/pages/ApplicationRemove.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import Page from "../components/Page";
import Spacer from "../components/Spacer";
import Text from "../components/Text";
import { AppContext } from "../contexts/AppContext";
import CallbackStateContextProvider from "../contexts/CallbackStateContext";
import { useAppRemove } from "../hooks/applications";
import { GrpcErrorCodes } from "../lib/types";
import { poller } from "../lib/utils";
import { GrpcErrorCodes, PageRoute } from "../lib/types";
import { formatURL, poller } from "../lib/utils";

type Props = {
className?: string;
Expand Down Expand Up @@ -78,6 +79,14 @@ function ApplicationRemove({ className, name }: Props) {
const [authOpen, setAuthOpen] = React.useState(false);
const [authSuccess, setAuthSuccess] = React.useState(false);
const [appError, setAppError] = React.useState(null);
const { getCallbackState, clearCallbackState } = React.useContext(AppContext);

const callbackState = getCallbackState();

if (callbackState) {
setAuthSuccess(true);
clearCallbackState();
}

React.useEffect(() => {
(async () => {
Expand Down Expand Up @@ -138,40 +147,48 @@ function ApplicationRemove({ className, name }: Props) {
</Page>
);
}

return (
<Page className={className}>
{!authSuccess &&
error &&
(error.code === GrpcErrorCodes.Unauthenticated ? (
<AuthAlert
title="Error"
provider={repoInfo.provider}
onClick={() => setAuthOpen(true)}
/>
) : (
<Alert title="Error" message={error?.message} />
))}
{repoRemoving && (
<Flex wide center align>
<CircularProgress />
<Spacer margin="small" />
<div>Remove operation in progress...</div>
</Flex>
)}
{!repoRemoveRes && !repoRemoving && !removedFromCluster && (
<Prompt name={name} onRemove={handleRemoveClick} />
)}
{(repoRemoving || repoRemoveRes) && (
<RepoRemoveStatus done={!repoRemoving} />
)}
<Spacer margin="small" />
{repoRemoveRes && <ClusterRemoveStatus done={removedFromCluster} />}
<GithubDeviceAuthModal
onSuccess={handleAuthSuccess}
onClose={() => setAuthOpen(false)}
open={authOpen}
repoName={name}
/>
<CallbackStateContextProvider
callbackState={{
page: formatURL(PageRoute.ApplicationRemove, { name }),
state: { authSuccess: false },
}}
>
{!authSuccess &&
error &&
(error.code === GrpcErrorCodes.Unauthenticated ? (
<AuthAlert
title="Error"
provider={repoInfo.provider}
onClick={() => setAuthOpen(true)}
/>
) : (
<Alert title="Error" message={error?.message} />
))}
{repoRemoving && (
<Flex wide center align>
<CircularProgress />
<Spacer margin="small" />
<div>Remove operation in progress...</div>
</Flex>
)}
{!repoRemoveRes && !repoRemoving && !removedFromCluster && (
<Prompt name={name} onRemove={handleRemoveClick} />
)}
{(repoRemoving || repoRemoveRes) && (
<RepoRemoveStatus done={!repoRemoving} />
)}
<Spacer margin="small" />
{repoRemoveRes && <ClusterRemoveStatus done={removedFromCluster} />}
<GithubDeviceAuthModal
onSuccess={handleAuthSuccess}
onClose={() => setAuthOpen(false)}
open={authOpen}
repoName={name}
/>
</CallbackStateContextProvider>
</Page>
);
}
Expand Down

0 comments on commit 586e6f9

Please sign in to comment.