aboutsummaryrefslogtreecommitdiffstats
path: root/webui
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2021-04-23 00:16:57 +0200
committerGitHub <noreply@github.com>2021-04-23 00:16:57 +0200
commita8f3b55986982db5f7c3918acaba2c214c919d11 (patch)
tree19706066a71c32684979b019aa62525481ff4891 /webui
parent271ca35d19a9649f6d0512760aed70c8778822fc (diff)
parent774bae6d432c13cfa0de3ddc2f111927743243fe (diff)
downloadgit-bug-a8f3b55986982db5f7c3918acaba2c214c919d11.tar.gz
Merge pull request #605 from GlancingMind/upstream-12-allow-users-to-inspect-their-current-identity-details
WebUI: Add user profile
Diffstat (limited to 'webui')
-rw-r--r--webui/src/App.tsx2
-rw-r--r--webui/src/components/Author.tsx12
-rw-r--r--webui/src/components/BugTitleForm/BugTitleForm.tsx8
-rw-r--r--webui/src/components/CurrentIdentity/CurrentIdentity.tsx31
-rw-r--r--webui/src/components/Header/Header.tsx2
-rw-r--r--webui/src/components/Identity/CurrentIdentity.graphql (renamed from webui/src/components/CurrentIdentity/CurrentIdentity.graphql)5
-rw-r--r--webui/src/components/Identity/CurrentIdentity.tsx114
-rw-r--r--webui/src/components/Identity/IdentityFragment.graphql10
-rw-r--r--webui/src/components/Identity/UserIdentity.graphql9
-rw-r--r--webui/src/components/IfLoggedIn/IfLoggedIn.tsx2
-rw-r--r--webui/src/graphql/fragments.graphql2
-rw-r--r--webui/src/pages/bug/LabelChange.tsx8
-rw-r--r--webui/src/pages/bug/Message.tsx1
-rw-r--r--webui/src/pages/bug/SetStatus.tsx8
-rw-r--r--webui/src/pages/bug/SetTitle.tsx8
-rw-r--r--webui/src/pages/identity/BugList.tsx73
-rw-r--r--webui/src/pages/identity/GetBugsByUser.graphql12
-rw-r--r--webui/src/pages/identity/GetUserStatistic.graphql13
-rw-r--r--webui/src/pages/identity/Identity.tsx147
-rw-r--r--webui/src/pages/identity/IdentityQuery.tsx24
-rw-r--r--webui/src/pages/identity/index.tsx1
-rw-r--r--webui/src/pages/list/BugRow.tsx6
-rw-r--r--webui/src/pages/list/ListQuery.tsx2
-rw-r--r--webui/src/pages/new/NewBug.graphql4
-rw-r--r--webui/src/pages/new/NewBugPage.tsx2
25 files changed, 448 insertions, 58 deletions
diff --git a/webui/src/App.tsx b/webui/src/App.tsx
index 4c81913c..04da655a 100644
--- a/webui/src/App.tsx
+++ b/webui/src/App.tsx
@@ -3,6 +3,7 @@ import { Route, Switch } from 'react-router';
import Layout from './components/Header';
import BugPage from './pages/bug';
+import IdentityPage from './pages/identity';
import ListPage from './pages/list';
import NewBugPage from './pages/new/NewBugPage';
import NotFoundPage from './pages/notfound/NotFoundPage';
@@ -14,6 +15,7 @@ export default function App() {
<Route path="/" exact component={ListPage} />
<Route path="/new" exact component={NewBugPage} />
<Route path="/bug/:id" exact component={BugPage} />
+ <Route path="/user/:id" exact component={IdentityPage} />
<Route component={NotFoundPage} />
</Switch>
</Layout>
diff --git a/webui/src/components/Author.tsx b/webui/src/components/Author.tsx
index d60e8969..593b5d36 100644
--- a/webui/src/components/Author.tsx
+++ b/webui/src/components/Author.tsx
@@ -1,6 +1,8 @@
import React from 'react';
+import { Link as RouterLink } from 'react-router-dom';
import MAvatar from '@material-ui/core/Avatar';
+import Link from '@material-ui/core/Link';
import Tooltip from '@material-ui/core/Tooltip/Tooltip';
import { AuthoredFragment } from '../graphql/fragments.generated';
@@ -11,13 +13,11 @@ type Props = AuthoredFragment & {
};
const Author = ({ author, ...props }: Props) => {
- if (!author.email) {
- return <span {...props}>{author.displayName}</span>;
- }
-
return (
- <Tooltip title={author.email}>
- <span {...props}>{author.displayName}</span>
+ <Tooltip title={`Goto the ${author.displayName}'s profile.`}>
+ <Link {...props} component={RouterLink} to={`/user/${author.id}`}>
+ {author.displayName}
+ </Link>
</Tooltip>
);
};
diff --git a/webui/src/components/BugTitleForm/BugTitleForm.tsx b/webui/src/components/BugTitleForm/BugTitleForm.tsx
index 665ecd4c..2a3c4db0 100644
--- a/webui/src/components/BugTitleForm/BugTitleForm.tsx
+++ b/webui/src/components/BugTitleForm/BugTitleForm.tsx
@@ -52,6 +52,10 @@ const useStyles = makeStyles((theme) => ({
saveButton: {
marginRight: theme.spacing(1),
},
+ author: {
+ fontWeight: 'bold',
+ color: theme.palette.text.secondary,
+ },
}));
interface Props {
@@ -86,7 +90,7 @@ function BugTitleForm({ bug }: Props) {
setTitle({
variables: {
input: {
- prefix: bug.humanId,
+ prefix: bug.id,
title: issueTitleInput.value,
},
},
@@ -182,7 +186,7 @@ function BugTitleForm({ bug }: Props) {
{bugTitleEdition ? editableBugTitle() : readonlyBugTitle()}
<div className="classes.headerSubtitle">
<Typography color={'textSecondary'}>
- <Author author={bug.author} />
+ <Author author={bug.author} className={classes.author} />
{' opened this bug '}
<Date date={bug.createdAt} />
</Typography>
diff --git a/webui/src/components/CurrentIdentity/CurrentIdentity.tsx b/webui/src/components/CurrentIdentity/CurrentIdentity.tsx
deleted file mode 100644
index 8cd3585b..00000000
--- a/webui/src/components/CurrentIdentity/CurrentIdentity.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-
-import Avatar from '@material-ui/core/Avatar';
-import { makeStyles } from '@material-ui/core/styles';
-
-import { useCurrentIdentityQuery } from './CurrentIdentity.generated';
-
-const useStyles = makeStyles((theme) => ({
- displayName: {
- marginLeft: theme.spacing(2),
- },
-}));
-
-const CurrentIdentity = () => {
- const classes = useStyles();
- const { loading, error, data } = useCurrentIdentityQuery();
-
- if (error || loading || !data?.repository?.userIdentity) return null;
-
- const user = data.repository.userIdentity;
- return (
- <>
- <Avatar src={user.avatarUrl ? user.avatarUrl : undefined}>
- {user.displayName.charAt(0).toUpperCase()}
- </Avatar>
- <div className={classes.displayName}>{user.displayName}</div>
- </>
- );
-};
-
-export default CurrentIdentity;
diff --git a/webui/src/components/Header/Header.tsx b/webui/src/components/Header/Header.tsx
index 63146cc9..56b35968 100644
--- a/webui/src/components/Header/Header.tsx
+++ b/webui/src/components/Header/Header.tsx
@@ -8,7 +8,7 @@ import Toolbar from '@material-ui/core/Toolbar';
import Tooltip from '@material-ui/core/Tooltip/Tooltip';
import { makeStyles } from '@material-ui/core/styles';
-import CurrentIdentity from '../CurrentIdentity/CurrentIdentity';
+import CurrentIdentity from '../Identity/CurrentIdentity';
import { LightSwitch } from '../Themer';
const useStyles = makeStyles((theme) => ({
diff --git a/webui/src/components/CurrentIdentity/CurrentIdentity.graphql b/webui/src/components/Identity/CurrentIdentity.graphql
index 2794a40f..054190df 100644
--- a/webui/src/components/CurrentIdentity/CurrentIdentity.graphql
+++ b/webui/src/components/Identity/CurrentIdentity.graphql
@@ -1,8 +1,9 @@
+#import "./IdentityFragment.graphql"
+
query CurrentIdentity {
repository {
userIdentity {
- displayName
- avatarUrl
+ ...Identity
}
}
}
diff --git a/webui/src/components/Identity/CurrentIdentity.tsx b/webui/src/components/Identity/CurrentIdentity.tsx
new file mode 100644
index 00000000..2d62dcdb
--- /dev/null
+++ b/webui/src/components/Identity/CurrentIdentity.tsx
@@ -0,0 +1,114 @@
+import React from 'react';
+import { Link as RouterLink } from 'react-router-dom';
+
+import {
+ Button,
+ ClickAwayListener,
+ Grow,
+ Link,
+ MenuItem,
+ MenuList,
+ Paper,
+ Popper,
+} from '@material-ui/core';
+import Avatar from '@material-ui/core/Avatar';
+import { makeStyles } from '@material-ui/core/styles';
+import LockIcon from '@material-ui/icons/Lock';
+
+import { useCurrentIdentityQuery } from './CurrentIdentity.generated';
+
+const useStyles = makeStyles((theme) => ({
+ displayName: {
+ marginLeft: theme.spacing(2),
+ },
+ hidden: {
+ display: 'none',
+ },
+ profileLink: {
+ ...theme.typography.button,
+ },
+ popupButton: {
+ textTransform: 'none',
+ color: theme.palette.primary.contrastText,
+ },
+}));
+
+const CurrentIdentity = () => {
+ const classes = useStyles();
+ const { loading, error, data } = useCurrentIdentityQuery();
+
+ const [open, setOpen] = React.useState(false);
+ const anchorRef = React.useRef<HTMLButtonElement>(null);
+
+ if (error || loading || !data?.repository?.userIdentity) return null;
+
+ const user = data.repository.userIdentity;
+
+ const handleToggle = () => {
+ setOpen((prevOpen) => !prevOpen);
+ };
+
+ const handleClose = (event: any) => {
+ if (anchorRef.current && anchorRef.current.contains(event.target)) {
+ return;
+ }
+ setOpen(false);
+ };
+
+ return (
+ <>
+ <Button
+ ref={anchorRef}
+ aria-controls={open ? 'menu-list-grow' : undefined}
+ aria-haspopup="true"
+ onClick={handleToggle}
+ className={classes.popupButton}
+ >
+ <Avatar src={user.avatarUrl ? user.avatarUrl : undefined}>
+ {user.displayName.charAt(0).toUpperCase()}
+ </Avatar>
+ <div className={classes.displayName}>{user.displayName}</div>
+ <LockIcon
+ color="secondary"
+ className={user.isProtected ? '' : classes.hidden}
+ />
+ </Button>
+ <Popper
+ open={open}
+ anchorEl={anchorRef.current}
+ role={undefined}
+ transition
+ disablePortal
+ >
+ {({ TransitionProps, placement }) => (
+ <Grow
+ {...TransitionProps}
+ style={{
+ transformOrigin:
+ placement === 'bottom' ? 'center top' : 'center bottom',
+ }}
+ >
+ <Paper>
+ <ClickAwayListener onClickAway={handleClose}>
+ <MenuList autoFocusItem={open} id="menu-list-grow">
+ <MenuItem>
+ <Link
+ color="inherit"
+ className={classes.profileLink}
+ component={RouterLink}
+ to={`/user/${user.id}`}
+ >
+ Open profile
+ </Link>
+ </MenuItem>
+ </MenuList>
+ </ClickAwayListener>
+ </Paper>
+ </Grow>
+ )}
+ </Popper>
+ </>
+ );
+};
+
+export default CurrentIdentity;
diff --git a/webui/src/components/Identity/IdentityFragment.graphql b/webui/src/components/Identity/IdentityFragment.graphql
new file mode 100644
index 00000000..6c4e2483
--- /dev/null
+++ b/webui/src/components/Identity/IdentityFragment.graphql
@@ -0,0 +1,10 @@
+fragment Identity on Identity {
+ id
+ humanId
+ displayName
+ email
+ name
+ avatarUrl
+ isProtected
+ login
+}
diff --git a/webui/src/components/Identity/UserIdentity.graphql b/webui/src/components/Identity/UserIdentity.graphql
new file mode 100644
index 00000000..9cf10248
--- /dev/null
+++ b/webui/src/components/Identity/UserIdentity.graphql
@@ -0,0 +1,9 @@
+#import "./IdentityFragment.graphql"
+
+query GetUserById($userId: String!) {
+ repository {
+ identity(prefix: $userId) {
+ ...Identity
+ }
+ }
+}
diff --git a/webui/src/components/IfLoggedIn/IfLoggedIn.tsx b/webui/src/components/IfLoggedIn/IfLoggedIn.tsx
index 2476aad8..ce120da1 100644
--- a/webui/src/components/IfLoggedIn/IfLoggedIn.tsx
+++ b/webui/src/components/IfLoggedIn/IfLoggedIn.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { useCurrentIdentityQuery } from '../CurrentIdentity/CurrentIdentity.generated';
+import { useCurrentIdentityQuery } from '../Identity/CurrentIdentity.generated';
type Props = { children: () => React.ReactNode };
const IfLoggedIn = ({ children }: Props) => {
diff --git a/webui/src/graphql/fragments.graphql b/webui/src/graphql/fragments.graphql
index 03a235f9..227d00b2 100644
--- a/webui/src/graphql/fragments.graphql
+++ b/webui/src/graphql/fragments.graphql
@@ -15,5 +15,7 @@ fragment authored on Authored {
email
displayName
avatarUrl
+ humanId
+ id
}
}
diff --git a/webui/src/pages/bug/LabelChange.tsx b/webui/src/pages/bug/LabelChange.tsx
index 712c33fa..868d8c9b 100644
--- a/webui/src/pages/bug/LabelChange.tsx
+++ b/webui/src/pages/bug/LabelChange.tsx
@@ -1,5 +1,6 @@
import React from 'react';
+import { Typography } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import Author from 'src/components/Author';
@@ -10,11 +11,12 @@ import { LabelChangeFragment } from './LabelChangeFragment.generated';
const useStyles = makeStyles((theme) => ({
main: {
- ...theme.typography.body2,
+ color: theme.palette.text.secondary,
marginLeft: theme.spacing(1) + 40,
},
author: {
fontWeight: 'bold',
+ color: theme.palette.text.secondary,
},
label: {
maxWidth: '50ch',
@@ -31,7 +33,7 @@ function LabelChange({ op }: Props) {
const { added, removed } = op;
const classes = useStyles();
return (
- <div className={classes.main}>
+ <Typography className={classes.main}>
<Author author={op.author} className={classes.author} />
{added.length > 0 && <span> added the </span>}
{added.map((label, index) => (
@@ -48,7 +50,7 @@ function LabelChange({ op }: Props) {
{added.length + removed.length > 1 && 's'}{' '}
</span>
<Date date={op.date} />
- </div>
+ </Typography>
);
}
diff --git a/webui/src/pages/bug/Message.tsx b/webui/src/pages/bug/Message.tsx
index 39b11ccd..808bb525 100644
--- a/webui/src/pages/bug/Message.tsx
+++ b/webui/src/pages/bug/Message.tsx
@@ -21,6 +21,7 @@ import MessageHistoryDialog from './MessageHistoryDialog';
const useStyles = makeStyles((theme) => ({
author: {
fontWeight: 'bold',
+ color: theme.palette.info.contrastText,
},
container: {
display: 'flex',
diff --git a/webui/src/pages/bug/SetStatus.tsx b/webui/src/pages/bug/SetStatus.tsx
index 855848f9..f231b917 100644
--- a/webui/src/pages/bug/SetStatus.tsx
+++ b/webui/src/pages/bug/SetStatus.tsx
@@ -1,5 +1,6 @@
import React from 'react';
+import { Typography } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import { Status } from '../../gqlTypes';
@@ -10,11 +11,12 @@ import { SetStatusFragment } from './SetStatusFragment.generated';
const useStyles = makeStyles((theme) => ({
main: {
- ...theme.typography.body2,
+ color: theme.palette.text.secondary,
marginLeft: theme.spacing(1) + 40,
},
author: {
fontWeight: 'bold',
+ color: theme.palette.text.secondary,
},
}));
@@ -29,11 +31,11 @@ function SetStatus({ op }: Props) {
];
return (
- <div className={classes.main}>
+ <Typography className={classes.main}>
<Author author={op.author} className={classes.author} />
<span> {status} this </span>
<Date date={op.date} />
- </div>
+ </Typography>
);
}
diff --git a/webui/src/pages/bug/SetTitle.tsx b/webui/src/pages/bug/SetTitle.tsx
index 98bea928..057062f7 100644
--- a/webui/src/pages/bug/SetTitle.tsx
+++ b/webui/src/pages/bug/SetTitle.tsx
@@ -1,5 +1,6 @@
import React from 'react';
+import { Typography } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import Author from 'src/components/Author';
@@ -9,11 +10,12 @@ import { SetTitleFragment } from './SetTitleFragment.generated';
const useStyles = makeStyles((theme) => ({
main: {
- ...theme.typography.body2,
+ color: theme.palette.text.secondary,
marginLeft: theme.spacing(1) + 40,
},
author: {
fontWeight: 'bold',
+ color: theme.palette.text.secondary,
},
before: {
fontWeight: 'bold',
@@ -31,14 +33,14 @@ type Props = {
function SetTitle({ op }: Props) {
const classes = useStyles();
return (
- <div className={classes.main}>
+ <Typography className={classes.main}>
<Author author={op.author} className={classes.author} />
<span> changed the title from </span>
<span className={classes.before}>{op.was}</span>
<span> to </span>
<span className={classes.after}>{op.title}</span>&nbsp;
<Date date={op.date} />
- </div>
+ </Typography>
);
}
diff --git a/webui/src/pages/identity/BugList.tsx b/webui/src/pages/identity/BugList.tsx
new file mode 100644
index 00000000..fbddb0fe
--- /dev/null
+++ b/webui/src/pages/identity/BugList.tsx
@@ -0,0 +1,73 @@
+import React from 'react';
+
+import { Card, Divider, Link, Typography } from '@material-ui/core';
+import CircularProgress from '@material-ui/core/CircularProgress';
+import { makeStyles } from '@material-ui/core/styles';
+
+import Date from '../../components/Date';
+
+import { useGetBugsByUserQuery } from './GetBugsByUser.generated';
+
+const useStyles = makeStyles((theme) => ({
+ main: {
+ ...theme.typography.body2,
+ },
+ bugLink: {
+ ...theme.typography.button,
+ },
+ cards: {
+ backgroundColor: theme.palette.background.default,
+ color: theme.palette.info.contrastText,
+ padding: theme.spacing(1),
+ margin: theme.spacing(1),
+ },
+}));
+
+type Props = {
+ id: string;
+};
+
+function BugList({ id }: Props) {
+ const classes = useStyles();
+ const { loading, error, data } = useGetBugsByUserQuery({
+ variables: {
+ query: 'author:' + id + ' sort:creation',
+ },
+ });
+
+ if (loading) return <CircularProgress />;
+ if (error) return <p>Error: {error}</p>;
+ const bugs = data?.repository?.allBugs.nodes;
+
+ return (
+ <div className={classes.main}>
+ {bugs?.map((bug, index) => {
+ return (
+ <Card className={classes.cards} key={index}>
+ <Typography variant="overline" component="h2">
+ <Link
+ className={classes.bugLink}
+ href={'/bug/' + bug.id}
+ color={'inherit'}
+ >
+ {bug.title}
+ </Link>
+ </Typography>
+ <Divider />
+ <Typography variant="subtitle2">
+ Created&nbsp;
+ <Date date={bug.createdAt} />
+ </Typography>
+ <Typography variant="subtitle2">
+ Last edited&nbsp;
+ <Date date={bug.createdAt} />
+ </Typography>
+ </Card>
+ );
+ })}
+ {bugs?.length === 0 && <p>No authored bugs by this user found.</p>}
+ </div>
+ );
+}
+
+export default BugList;
diff --git a/webui/src/pages/identity/GetBugsByUser.graphql b/webui/src/pages/identity/GetBugsByUser.graphql
new file mode 100644
index 00000000..0f170dc1
--- /dev/null
+++ b/webui/src/pages/identity/GetBugsByUser.graphql
@@ -0,0 +1,12 @@
+query GetBugsByUser ($query: String){
+ repository {
+ allBugs(query: $query) {
+ nodes {
+ id
+ title
+ createdAt
+ lastEdit
+ }
+ }
+ }
+}
diff --git a/webui/src/pages/identity/GetUserStatistic.graphql b/webui/src/pages/identity/GetUserStatistic.graphql
new file mode 100644
index 00000000..318b860d
--- /dev/null
+++ b/webui/src/pages/identity/GetUserStatistic.graphql
@@ -0,0 +1,13 @@
+query GetUserStatistic($authorQuery: String!, $participantQuery: String!, $actionQuery: String!) {
+ repository {
+ authored: allBugs(query: $authorQuery) {
+ totalCount
+ },
+ participated: allBugs(query: $participantQuery) {
+ totalCount
+ }
+ actions: allBugs(query: $actionQuery) {
+ totalCount
+ }
+ }
+}
diff --git a/webui/src/pages/identity/Identity.tsx b/webui/src/pages/identity/Identity.tsx
new file mode 100644
index 00000000..5170eeea
--- /dev/null
+++ b/webui/src/pages/identity/Identity.tsx
@@ -0,0 +1,147 @@
+import React from 'react';
+import { Link as RouterLink } from 'react-router-dom';
+
+import { Link, Paper, Typography } from '@material-ui/core';
+import Avatar from '@material-ui/core/Avatar';
+import CircularProgress from '@material-ui/core/CircularProgress';
+import Grid from '@material-ui/core/Grid';
+import { makeStyles } from '@material-ui/core/styles';
+import InfoIcon from '@material-ui/icons/Info';
+import MailOutlineIcon from '@material-ui/icons/MailOutline';
+
+import { IdentityFragment } from '../../components/Identity/IdentityFragment.generated';
+
+import { useGetUserStatisticQuery } from './GetUserStatistic.generated';
+
+const useStyles = makeStyles((theme) => ({
+ main: {
+ maxWidth: 1000,
+ margin: 'auto',
+ marginTop: theme.spacing(3),
+ },
+ content: {
+ padding: theme.spacing(0.5, 2, 2, 2),
+ wordWrap: 'break-word',
+ },
+ large: {
+ minWidth: 200,
+ minHeight: 200,
+ margin: 'auto',
+ maxWidth: '100%',
+ maxHeight: '100%',
+ },
+ heading: {
+ marginTop: theme.spacing(3),
+ },
+ header: {
+ ...theme.typography.h4,
+ wordBreak: 'break-word',
+ },
+ infoIcon: {
+ verticalAlign: 'bottom',
+ },
+}));
+
+type Props = {
+ identity: IdentityFragment;
+};
+const Identity = ({ identity }: Props) => {
+ const classes = useStyles();
+ const user = identity;
+
+ const { loading, error, data } = useGetUserStatisticQuery({
+ variables: {
+ authorQuery: 'author:' + user?.id,
+ participantQuery: 'participant:' + user?.id,
+ actionQuery: 'actor:' + user?.id,
+ },
+ });
+
+ if (loading) return <CircularProgress />;
+ if (error) return <p>Error: {error}</p>;
+ const statistic = data?.repository;
+ const authoredCount = statistic?.authored?.totalCount;
+ const participatedCount = statistic?.participated?.totalCount;
+ const actionCount = statistic?.actions?.totalCount;
+
+ return (
+ <main className={classes.main}>
+ <Paper elevation={3} className={classes.content}>
+ <Grid spacing={2} container direction="row">
+ <Grid xs={12} sm={4} className={classes.heading} item>
+ <Avatar
+ src={user?.avatarUrl ? user.avatarUrl : undefined}
+ className={classes.large}
+ >
+ {user?.displayName.charAt(0).toUpperCase()}
+ </Avatar>
+ </Grid>
+ <Grid xs={12} sm={4} item>
+ <section>
+ <h1 className={classes.header}>{user?.name}</h1>
+ <Typography variant="subtitle1">
+ Name: {user?.displayName ? user?.displayName : '---'}
+ </Typography>
+ <Typography variant="subtitle1">
+ Id (truncated): {user?.humanId ? user?.humanId : '---'}
+ <InfoIcon
+ titleAccess={user?.id ? user?.id : '---'}
+ className={classes.infoIcon}
+ />
+ </Typography>
+ {user?.email && (
+ <Typography
+ variant="subtitle1"
+ style={{
+ display: 'flex',
+ alignItems: 'center',
+ flexWrap: 'wrap',
+ }}
+ >
+ <MailOutlineIcon />
+ <Link href={'mailto:' + user?.email} color={'inherit'}>
+ {user?.email}
+ </Link>
+ </Typography>
+ )}
+ </section>
+ </Grid>
+ <Grid xs={12} sm={4} item>
+ <section>
+ <h1 className={classes.header}>Statistics</h1>
+ <Link
+ component={RouterLink}
+ to={`/?q=author%3A${user?.id}+sort%3Acreation`}
+ color={'inherit'}
+ >
+ <Typography variant="subtitle1">
+ Created {authoredCount} bugs.
+ </Typography>
+ </Link>
+ <Link
+ component={RouterLink}
+ to={`/?q=participant%3A${user?.id}+sort%3Acreation`}
+ color={'inherit'}
+ >
+ <Typography variant="subtitle1">
+ Participated to {participatedCount} bugs.
+ </Typography>
+ </Link>
+ <Link
+ component={RouterLink}
+ to={`/?q=actor%3A${user?.id}+sort%3Acreation`}
+ color={'inherit'}
+ >
+ <Typography variant="subtitle1">
+ Interacted with {actionCount} bugs.
+ </Typography>
+ </Link>
+ </section>
+ </Grid>
+ </Grid>
+ </Paper>
+ </main>
+ );
+};
+
+export default Identity;
diff --git a/webui/src/pages/identity/IdentityQuery.tsx b/webui/src/pages/identity/IdentityQuery.tsx
new file mode 100644
index 00000000..964a9bac
--- /dev/null
+++ b/webui/src/pages/identity/IdentityQuery.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { RouteComponentProps } from 'react-router-dom';
+
+import CircularProgress from '@material-ui/core/CircularProgress';
+
+import { useGetUserByIdQuery } from '../../components/Identity/UserIdentity.generated';
+
+import Identity from './Identity';
+
+type Props = RouteComponentProps<{
+ id: string;
+}>;
+
+const UserQuery: React.FC<Props> = ({ match }: Props) => {
+ const { loading, error, data } = useGetUserByIdQuery({
+ variables: { userId: match.params.id },
+ });
+ if (loading) return <CircularProgress />;
+ if (error) return <p>Error: {error}</p>;
+ if (!data?.repository?.identity) return <p>404.</p>;
+ return <Identity identity={data.repository.identity} />;
+};
+
+export default UserQuery;
diff --git a/webui/src/pages/identity/index.tsx b/webui/src/pages/identity/index.tsx
new file mode 100644
index 00000000..06208687
--- /dev/null
+++ b/webui/src/pages/identity/index.tsx
@@ -0,0 +1 @@
+export { default } from './IdentityQuery';
diff --git a/webui/src/pages/list/BugRow.tsx b/webui/src/pages/list/BugRow.tsx
index 87e45581..562149f3 100644
--- a/webui/src/pages/list/BugRow.tsx
+++ b/webui/src/pages/list/BugRow.tsx
@@ -9,6 +9,7 @@ import CheckCircleOutline from '@material-ui/icons/CheckCircleOutline';
import CommentOutlinedIcon from '@material-ui/icons/CommentOutlined';
import ErrorOutline from '@material-ui/icons/ErrorOutline';
+import Author from 'src/components/Author';
import Date from 'src/components/Date';
import Label from 'src/components/Label';
import { Status } from 'src/gqlTypes';
@@ -105,7 +106,7 @@ function BugRow({ bug }: Props) {
<TableCell className={classes.cell}>
<BugStatus status={bug.status} className={classes.status} />
<div className={classes.expand}>
- <Link to={'bug/' + bug.humanId}>
+ <Link to={'bug/' + bug.id}>
<div className={classes.bugTitleWrapper}>
<span className={classes.title}>{bug.title}</span>
{bug.labels.length > 0 &&
@@ -117,7 +118,8 @@ function BugRow({ bug }: Props) {
<div className={classes.details}>
{bug.humanId} opened&nbsp;
<Date date={bug.createdAt} />
- &nbsp;by {bug.author.displayName}
+ &nbsp;by&nbsp;
+ <Author className={classes.details} author={bug.author} />
</div>
</div>
<span className={classes.commentCountCell}>
diff --git a/webui/src/pages/list/ListQuery.tsx b/webui/src/pages/list/ListQuery.tsx
index 2b46dca5..9aefd02d 100644
--- a/webui/src/pages/list/ListQuery.tsx
+++ b/webui/src/pages/list/ListQuery.tsx
@@ -14,7 +14,7 @@ import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft';
import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight';
import Skeleton from '@material-ui/lab/Skeleton';
-import { useCurrentIdentityQuery } from '../../components/CurrentIdentity/CurrentIdentity.generated';
+import { useCurrentIdentityQuery } from '../../components/Identity/CurrentIdentity.generated';
import IfLoggedIn from 'src/components/IfLoggedIn/IfLoggedIn';
import { parse, Query, stringify } from './Filter';
diff --git a/webui/src/pages/new/NewBug.graphql b/webui/src/pages/new/NewBug.graphql
index 92df016e..ef024e41 100644
--- a/webui/src/pages/new/NewBug.graphql
+++ b/webui/src/pages/new/NewBug.graphql
@@ -1,7 +1,7 @@
mutation newBug($input: NewBugInput!) {
newBug(input: $input) {
bug {
- humanId
+ id
}
}
-} \ No newline at end of file
+}
diff --git a/webui/src/pages/new/NewBugPage.tsx b/webui/src/pages/new/NewBugPage.tsx
index 4dc60e3c..cdec3558 100644
--- a/webui/src/pages/new/NewBugPage.tsx
+++ b/webui/src/pages/new/NewBugPage.tsx
@@ -62,7 +62,7 @@ function NewBugPage() {
},
},
}).then(function (data) {
- const id = data.data?.newBug.bug.humanId;
+ const id = data.data?.newBug.bug.id;
history.push('/bug/' + id);
});
issueTitleInput.value = '';