Skip to content

Commit 6349d39

Browse files
authored
Dashboard page for webhook requests (#200)
1 parent cf89c5a commit 6349d39

File tree

17 files changed

+357
-53
lines changed

17 files changed

+357
-53
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,14 @@ GITLAB_AUTH_TOKEN="<token>" yarn run start
103103
| `HTTP_SERVER_ENABLE` | `false` | It'll enable experimental API and dashboard support |
104104
| `HTTP_SERVER_PORT` | `4000` | It'll use different http server port |
105105
| `WEB_HOOK_TOKEN` | `` | It'll enable experimental web hook support |
106+
| `WEB_HOOK_HISTORY_SIZE` | `100` | It's useful just primarily for debugging purposes. |
106107
| `ENABLE_PERMISSION_VALIDATION` | `false` | It'll enable experimental permission validation |
107108

108109
## Development
109110

110111
For web hook development use this:
111112

112113
```bash
113-
NGROK_AUTH=<authCode>
114-
docker run -it --rm --net=host -p 4040:4040 -e NGROK_AUTH="$NGROK_AUTH" wernight/ngrok ngrok http 4000
114+
export NGROK_AUTHTOKEN=<authCode>
115+
docker run -it --rm --net=host -p 4040:4040 -e NGROK_AUTHTOKEN="$NGROK_AUTHTOKEN" wernight/ngrok ngrok http 4000
115116
```

charts/gitlab-merger-bot/templates/deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ spec:
6363
value: "{{ .Values.settings.httpServerPort }}"
6464
- name: WEB_HOOK_TOKEN
6565
value: "{{ .Values.settings.webHookToken }}"
66+
- name: WEB_HOOK_HISTORY_SIZE
67+
value: "{{ .Values.settings.webHookHistorySize }}"
6668
- name: ENABLE_PERMISSION_VALIDATION
6769
value: "{{ .Values.settings.enablePermissionValidation }}"
6870
{{- if .Values.settings.httpProxy }}

charts/gitlab-merger-bot/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ settings:
8989
httpServerEnable: false
9090
httpServerPort: 4000
9191
webHookToken: ""
92+
webHookHistorySize: 100
9293
httpProxySecretName: http-proxy
9394
httpProxy: ""
9495
enablePermissionValidation: false

dashboard/src/components/layout.tsx

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,24 @@ import Typography from '@mui/material/Typography';
44
import Box from '@mui/material/Box';
55
import React from 'react';
66
import Container from '@mui/material/Container';
7-
import Paper from '@mui/material/Paper';
87
import Avatar from '@mui/material/Avatar';
98
import { useQuery } from '@apollo/client';
109
import { MeQuery } from '../types';
1110
import gql from 'graphql-tag';
11+
import { Tab, Tabs } from '@mui/material';
12+
import { useRouter } from 'next/router';
1213

13-
interface LayoutProps {
14-
children: React.ReactNode;
15-
}
14+
const pages = {
15+
'/': {
16+
label: 'Merge Queue',
17+
},
18+
'/web-hook-history': {
19+
label: 'Web Hook History',
20+
},
21+
};
1622

17-
export const Layout = ({ children }: LayoutProps) => {
23+
export const Layout = (children: React.ReactElement) => {
24+
const router = useRouter();
1825
const { data } = useQuery<MeQuery>(gql`
1926
query Me {
2027
me {
@@ -24,21 +31,34 @@ export const Layout = ({ children }: LayoutProps) => {
2431
}
2532
`);
2633

34+
const tabIndex = Object.keys(pages).findIndex((path) => router.pathname === path);
35+
2736
return (
2837
<>
29-
<AppBar position='relative'>
38+
<AppBar position='fixed'>
3039
<Toolbar>
3140
<Avatar alt='Remy Sharp' src={data?.me.avatarUrl} />
3241
&nbsp;&nbsp;&nbsp;
3342
<Typography variant='h6'>{data?.me.name}</Typography>
43+
<Tabs
44+
value={tabIndex}
45+
onChange={() => {}}
46+
textColor='inherit'
47+
sx={{
48+
px: 6,
49+
'& .MuiTabs-indicator': {
50+
backgroundColor: '#ffffff',
51+
},
52+
}}
53+
>
54+
{Object.entries(pages).map(([path, { label }]) => (
55+
<Tab key={path} label={label} onClick={() => router.push(path)} />
56+
))}
57+
</Tabs>
3458
</Toolbar>
3559
</AppBar>
3660
<Container maxWidth={'lg'}>
37-
<Paper elevation={3}>
38-
<Box mt={4} p={4}>
39-
{children}
40-
</Box>
41-
</Paper>
61+
<Box mt={12}>{children}</Box>
4262
</Container>
4363
</>
4464
);

dashboard/src/lib/apollo.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@ import {
1212
} from '@apollo/client';
1313
import { getMainDefinition } from '@apollo/client/utilities';
1414
import { HttpLink } from '@apollo/client/link/http';
15-
import { WebSocketLink } from '@apollo/client/link/ws';
1615

1716
type TApolloClient = ApolloClient<NormalizedCacheObject>;
1817

1918
type InitialProps = {
20-
apolloClient: TApolloClient;
21-
apolloState: any;
19+
apolloClient?: TApolloClient;
20+
apolloState?: any;
2221
} & Record<string, any>;
2322

2423
type WithApolloPageContext = {

dashboard/src/pages/_app.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,24 @@ import { AppCacheProvider } from '@mui/material-nextjs/v14-pagesRouter';
55
import { ThemeProvider } from '@mui/material/styles';
66
import CssBaseline from '@mui/material/CssBaseline';
77
import theme from '../theme';
8+
import { NextPage } from 'next';
9+
import { withApollo } from '../lib/apollo';
810

9-
export default function MyApp(props: AppProps) {
11+
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
12+
getLayout?: (page: React.ReactElement) => React.ReactNode;
13+
};
14+
15+
export type AppPropsWithLayout = AppProps & {
16+
Component: NextPageWithLayout;
17+
};
18+
19+
const Page = withApollo(((props: AppPropsWithLayout) => {
1020
const { Component, pageProps } = props;
21+
const getLayout = Component.getLayout ?? ((page) => page);
22+
return getLayout(<Component {...pageProps} />);
23+
}) as NextPage);
1124

25+
export default function MyApp(props: AppPropsWithLayout) {
1226
return (
1327
<AppCacheProvider {...props}>
1428
<Head>
@@ -20,7 +34,7 @@ export default function MyApp(props: AppProps) {
2034
</Head>
2135
<ThemeProvider theme={theme}>
2236
<CssBaseline />
23-
<Component {...pageProps} />
37+
<Page {...props} />
2438
</ThemeProvider>
2539
</AppCacheProvider>
2640
);

dashboard/src/pages/_document.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default function AppDocument(props: DocumentProps & DocumentHeadTagsProps
1919
/>
2020
<DocumentHeadTags {...props} />
2121
</Head>
22-
<body>
22+
<body style={{ backgroundColor: '#f8f8f8' }}>
2323
<Main />
2424
<NextScript />
2525
</body>

dashboard/src/pages/index.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import Toolbar from '@mui/material/Toolbar';
2525
import Icon from '@mui/icons-material/Send';
2626
import { UserAvatar } from '../components/ui/UserAvatar';
2727
import CircularProgress from '@mui/material/CircularProgress';
28+
import { NextPageWithLayout } from './_app';
2829

2930
const Row = (props: GetQueuesSubscriptionJobFragment) => {
3031
const [unassign, { loading }] = useMutation<UnassignMutation, UnassignMutationVariables>(gql`
@@ -112,7 +113,7 @@ const App = () => {
112113
`);
113114

114115
return (
115-
<Layout>
116+
<>
116117
<Typography variant={'h3'}>Queues</Typography>
117118
<Box mt={4}>
118119
{loading ? (
@@ -126,7 +127,7 @@ const App = () => {
126127
</Typography>
127128
</Toolbar>
128129

129-
<TableContainer component={Paper} key={queue.name}>
130+
<TableContainer key={queue.name}>
130131
<Table size='small' aria-label={queue.name}>
131132
<TableHead>
132133
<TableRow>
@@ -148,8 +149,10 @@ const App = () => {
148149
))
149150
)}
150151
</Box>
151-
</Layout>
152+
</>
152153
);
153154
};
154155

155-
export default withApollo(App);
156+
App.getLayout = Layout;
157+
158+
export default App;
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import React from 'react';
2+
import { withApollo } from '../lib/apollo';
3+
import Typography from '@mui/material/Typography';
4+
import Box from '@mui/material/Box';
5+
import Layout from '../components/layout';
6+
import { useSubscription } from '@apollo/client';
7+
import gql from 'graphql-tag';
8+
import OverlayLoading from '../components/ui/overlay-loading';
9+
import { GetWebHookHistorySubscriptionSubscription } from '../types';
10+
import Table from '@mui/material/Table';
11+
import TableBody from '@mui/material/TableBody';
12+
import TableCell from '@mui/material/TableCell';
13+
import TableContainer from '@mui/material/TableContainer';
14+
import TableHead from '@mui/material/TableHead';
15+
import TableRow from '@mui/material/TableRow';
16+
import Paper from '@mui/material/Paper';
17+
import Button from '@mui/material/Button';
18+
import Icon from '@mui/icons-material/Send';
19+
import { Collapse } from '@mui/material';
20+
import { KeyboardArrowDown, KeyboardArrowUp } from '@mui/icons-material';
21+
22+
const intl = new Intl.DateTimeFormat(undefined, {
23+
year: 'numeric',
24+
month: 'numeric',
25+
day: 'numeric',
26+
hour: 'numeric',
27+
minute: 'numeric',
28+
second: 'numeric',
29+
});
30+
31+
const Row = ({
32+
webHookHistory,
33+
}: {
34+
webHookHistory: GetWebHookHistorySubscriptionSubscription['webHookHistory'][number];
35+
}) => {
36+
const [open, setOpen] = React.useState(false);
37+
38+
return (
39+
<>
40+
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
41+
<TableCell component='th'>
42+
{intl.format(new Date(webHookHistory.createdAt * 1000))}
43+
</TableCell>
44+
<TableCell component='th'>{webHookHistory.status}</TableCell>
45+
<TableCell component='th'>{webHookHistory.event}</TableCell>
46+
<TableCell component='th'>
47+
{webHookHistory.data && webHookHistory.data.substr(0, 50)}
48+
</TableCell>
49+
<TableCell component='th'>
50+
<Button aria-label='expand row' size='small' onClick={() => setOpen(!open)}>
51+
{open ? <KeyboardArrowUp /> : <KeyboardArrowDown />}
52+
</Button>
53+
</TableCell>
54+
</TableRow>
55+
<TableRow>
56+
<TableCell
57+
sx={{ paddingBottom: 0, paddingTop: 0, overflow: 'auto', maxHeight: '400px' }}
58+
colSpan={5}
59+
>
60+
<Collapse in={open} timeout='auto' unmountOnExit>
61+
{webHookHistory.data && (
62+
<small>
63+
<pre style={{ maxHeight: '600px' }}>
64+
{JSON.stringify(JSON.parse(webHookHistory.data), null, 2)}
65+
</pre>
66+
</small>
67+
)}
68+
</Collapse>
69+
</TableCell>
70+
</TableRow>
71+
</>
72+
);
73+
};
74+
75+
const App = () => {
76+
const { loading, error, data } = useSubscription<GetWebHookHistorySubscriptionSubscription>(gql`
77+
subscription GetWebHookHistorySubscription {
78+
webHookHistory {
79+
createdAt
80+
data
81+
event
82+
status
83+
}
84+
}
85+
`);
86+
87+
return (
88+
<>
89+
<Typography variant={'h3'}>Web Hook History</Typography>
90+
<Box mt={4}>
91+
{loading ? (
92+
<OverlayLoading />
93+
) : (
94+
<TableContainer component={Paper}>
95+
<Table size='small'>
96+
<TableHead>
97+
<TableRow>
98+
<TableCell>Created at</TableCell>
99+
<TableCell>Status</TableCell>
100+
<TableCell>Event</TableCell>
101+
<TableCell>Data</TableCell>
102+
<TableCell></TableCell>
103+
</TableRow>
104+
</TableHead>
105+
<TableBody>
106+
{data?.webHookHistory.map((webHookHistory) => (
107+
<Row
108+
key={webHookHistory.createdAt}
109+
webHookHistory={webHookHistory}
110+
/>
111+
))}
112+
</TableBody>
113+
</Table>
114+
</TableContainer>
115+
)}
116+
</Box>
117+
</>
118+
);
119+
};
120+
121+
App.getLayout = Layout;
122+
123+
export default App;

schema.graphql

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,15 @@ enum JobPriority {
1515
NORMAL
1616
}
1717

18+
enum WebHookHistoryStatus {
19+
SKIPPED
20+
SUCCESS
21+
UNAUTHORIZED
22+
INVALID_EVENT
23+
}
24+
1825
enum JobStatus {
19-
IN_PROGRESSJobStatus
26+
IN_PROGRESS
2027
REBASING
2128
CHECKING_MERGE_STATUS
2229
WAITING
@@ -39,6 +46,13 @@ type Queue {
3946
jobs: [Job!]!
4047
}
4148

49+
type WebHookHistoryRequest {
50+
createdAt: Int!
51+
status: WebHookHistoryStatus!
52+
event: String
53+
data: String
54+
}
55+
4256
type User {
4357
id: Int!
4458
name: String!
@@ -60,6 +74,7 @@ type Query {
6074

6175
type Subscription {
6276
queues: [Queue!]!
77+
webHookHistory: [WebHookHistoryRequest!]!
6378
}
6479

6580
input UnassignInput {

0 commit comments

Comments
 (0)