Skip to content

Commit

Permalink
Improve UX of project SSO config form (#737)
Browse files Browse the repository at this point in the history
**What this PR does / why we need it**:
![image](https://user-images.githubusercontent.com/6136383/92120533-26823900-ee34-11ea-8028-52f39dd0fcd4.png)

![image](https://user-images.githubusercontent.com/6136383/92120546-2aae5680-ee34-11ea-8967-c0455a756a6a.png)


**Which issue(s) this PR fixes**:

Fixes #718

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
-->
```release-note
NONE
```

This PR was merged by Kapetanios.
  • Loading branch information
cakecatz committed Sep 3, 2020
1 parent 79070fc commit b00fe26
Show file tree
Hide file tree
Showing 10 changed files with 401 additions and 200 deletions.
14 changes: 9 additions & 5 deletions pkg/app/web/src/api/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,20 @@ export const updateGitHubSSO = ({
clientSecret,
baseUrl,
uploadUrl,
}: ProjectSSOConfig.GitHub.AsObject): Promise<
UpdateProjectSSOConfigResponse.AsObject
> => {
}: {
clientId: string;
clientSecret: string;
baseUrl?: string;
uploadUrl?: string;
}): Promise<UpdateProjectSSOConfigResponse.AsObject> => {
const req = new UpdateProjectSSOConfigRequest();
const sso = new ProjectSSOConfig();
const github = new ProjectSSOConfig.GitHub();
github.setClientId(clientId);
github.setClientSecret(clientSecret);
github.setBaseUrl(baseUrl);
github.setUploadUrl(uploadUrl);
baseUrl && github.setBaseUrl(baseUrl);
uploadUrl && github.setUploadUrl(uploadUrl);

sso.setGithub(github);
req.setSso(sso);
return apiRequest(req, apiClient.updateProjectSSOConfig);
Expand Down
11 changes: 1 addition & 10 deletions pkg/app/web/src/components/github-sso-form.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import React from "react";
import { GithubSSOForm } from "./github-sso-form";
import { action } from "@storybook/addon-actions";

export default {
title: "SETTINGS/GithubSSOForm",
component: GithubSSOForm,
};

export const overview: React.FC = () => (
<GithubSSOForm
onSave={(params) => {
action("onSave")(params);
return Promise.resolve();
}}
isSaving={false}
/>
);
export const overview: React.FC = () => <GithubSSOForm />;
247 changes: 111 additions & 136 deletions pkg/app/web/src/components/github-sso-form.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
import { IconButton, makeStyles, Typography } from "@material-ui/core";
import EditIcon from "@material-ui/icons/Edit";
import React, { FC, memo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { AppState } from "../modules";
import {
Button,
TextField,
Typography,
Dialog,
DialogContent,
DialogTitle,
DialogActions,
} from "@material-ui/core";
import React, { FC, useState } from "react";
fetchProject,
GitHubSSO,
Teams,
updateGitHubSSO,
updateRBAC,
} from "../modules/project";
import { AppDispatch } from "../store";
import { InputForm } from "./input-form";
import { SSOEditDialog } from "./sso-edit-dialog";

const useStyles = makeStyles((theme) => ({
indent: {
padding: theme.spacing(1),
},
name: {
color: theme.palette.text.secondary,
marginRight: theme.spacing(2),
minWidth: 120,
},
item: {
display: "flex",
alignItems: "center",
},
}));

export interface GitHubSSOFormParams {
clientId: string;
Expand All @@ -20,142 +40,97 @@ export interface GitHubSSOFormParams {
viewerTeam: string;
}

interface Props {
isSaving: boolean;
onSave: (params: GitHubSSOFormParams) => Promise<unknown>;
}

function hasEmptyValue(obj: Record<string, string>): boolean {
return Object.keys(obj).some((key) => obj[key] === "");
}

const initialParams = {
clientId: "",
clientSecret: "",
baseUrl: "",
uploadUrl: "",
org: "",
adminTeam: "",
editorTeam: "",
viewerTeam: "",
};

export const GithubSSOForm: FC<Props> = ({ isSaving, onSave }) => {
const [isOpen, setIsOpen] = useState(false);
const [params, setParams] = useState(initialParams);

const hasEmpty = hasEmptyValue(params);
export const GithubSSOForm: FC = memo(function GithubSSOForm() {
const classes = useStyles();
const dispatch = useDispatch<AppDispatch>();
const [isEditSSO, setIsEditSSO] = useState(false);
const teams = useSelector<AppState, Teams | null>(
(state) => state.project.teams
);
const sso = useSelector<AppState, GitHubSSO | null>(
(state) => state.project.github
);

const handleOnSave = (): void => {
onSave(params).then(() => {
setIsOpen(false);
setParams(initialParams);
const handleSaveTeams = (params: Partial<Teams>): void => {
dispatch(updateRBAC(params)).finally(() => {
dispatch(fetchProject());
});
};

const handleCancel = (): void => {
setIsOpen(false);
setParams(initialParams);
const handleSaveSSO = (
params: Partial<GitHubSSO> & { clientId: string; clientSecret: string }
): void => {
dispatch(updateGitHubSSO(params)).finally(() => {
dispatch(fetchProject());
});
};

return (
<div>
<>
<Typography variant="h6">GitHub</Typography>
<Button variant="contained" onClick={() => setIsOpen(true)}>
EDIT
</Button>

<Dialog open={isOpen}>
<DialogTitle>GitHub Single Sign On Setting</DialogTitle>
<DialogContent>
<TextField
label="Client ID"
margin="dense"
variant="outlined"
fullWidth
value={params.clientId}
onChange={(e) => setParams({ ...params, clientId: e.target.value })}
/>
<TextField
label="Client Secret"
margin="dense"
variant="outlined"
fullWidth
value={params.clientSecret}
onChange={(e) =>
setParams({ ...params, clientSecret: e.target.value })
}
/>
<TextField
label="Base URL"
margin="dense"
variant="outlined"
fullWidth
value={params.baseUrl}
onChange={(e) => setParams({ ...params, baseUrl: e.target.value })}
/>
<TextField
label="Upload URL"
margin="dense"
variant="outlined"
fullWidth
value={params.uploadUrl}
onChange={(e) =>
setParams({ ...params, uploadUrl: e.target.value })
}
<div className={classes.indent}>
<Typography variant="subtitle2">Team</Typography>
<div className={classes.indent}>
<InputForm
currentValue={teams?.admin}
name="Admin Team"
onSave={(value) => handleSaveTeams({ admin: value })}
/>
<TextField
label="Organization"
margin="dense"
variant="outlined"
fullWidth
value={params.org}
onChange={(e) => setParams({ ...params, org: e.target.value })}
<InputForm
currentValue={teams?.editor}
name="Editor Team"
onSave={(value) => handleSaveTeams({ editor: value })}
/>
<TextField
label="Admin Team"
margin="dense"
variant="outlined"
fullWidth
value={params.adminTeam}
onChange={(e) =>
setParams({ ...params, adminTeam: e.target.value })
}
<InputForm
currentValue={teams?.viewer}
name="Viewer Team"
onSave={(value) => handleSaveTeams({ viewer: value })}
/>
<TextField
label="Editor Team"
margin="dense"
variant="outlined"
fullWidth
value={params.editorTeam}
onChange={(e) =>
setParams({ ...params, editorTeam: e.target.value })
}
/>
<TextField
label="Viewer Team"
margin="dense"
variant="outlined"
fullWidth
value={params.viewerTeam}
onChange={(e) =>
setParams({ ...params, viewerTeam: e.target.value })
}
/>
<DialogActions>
<Button color="primary" onClick={handleCancel} disabled={isSaving}>
CANCEL
</Button>
<Button
color="primary"
onClick={handleOnSave}
disabled={isSaving || hasEmpty}
>
SAVE
</Button>
</DialogActions>
</DialogContent>
</Dialog>
</div>
</div>
</div>

<div className={classes.indent}>
<Typography variant="subtitle2">
SSO
<IconButton onClick={() => setIsEditSSO(true)}>
<EditIcon />
</IconButton>
<div className={classes.indent}>
<div className={classes.item}>
<Typography variant="subtitle1" className={classes.name}>
Client ID
</Typography>
<Typography variant="body1">{sso?.clientId}</Typography>
</div>

<div className={classes.item}>
<Typography variant="subtitle1" className={classes.name}>
Client Secret
</Typography>
<Typography variant="body1">{sso?.clientSecret}</Typography>
</div>
<div className={classes.item}>
<Typography variant="subtitle1" className={classes.name}>
Base URL
</Typography>
<Typography variant="body1">{sso?.baseUrl}</Typography>
</div>
<div className={classes.item}>
<Typography variant="subtitle1" className={classes.name}>
Upload URL
</Typography>
<Typography variant="body1">{sso?.uploadUrl}</Typography>
</div>
</div>
</Typography>
</div>
<SSOEditDialog
currentBaseURL={sso?.baseUrl ?? ""}
currentUploadURL={sso?.uploadUrl ?? ""}
onSave={handleSaveSSO}
open={isEditSSO}
onClose={() => setIsEditSSO(false)}
/>
</>
);
};
});
21 changes: 21 additions & 0 deletions pkg/app/web/src/components/input-form.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from "react";
import { InputForm } from "./input-form";
import { action } from "@storybook/addon-actions";

export default {
title: "InputForm",
component: InputForm,
};

export const overview: React.FC = () => (
<InputForm name="Name" currentValue="value" onSave={action("onChange")} />
);

export const IsSecret: React.FC = () => (
<InputForm
name="Name"
currentValue="value"
onSave={action("onChange")}
isSecret
/>
);

0 comments on commit b00fe26

Please sign in to comment.