-
Notifications
You must be signed in to change notification settings - Fork 62
feat: get creating, updating, deleting Integrations working #240
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { snakecaseKeys } from './utils/snakecase'; | ||
|
||
export const API_HOST = 'http://localhost:8080'; | ||
|
||
// TODO: get this on the server-side from the session | ||
export const APPLICATION_ID = 'a4398523-03a2-42dd-9681-c91e3e2efaf4'; | ||
|
||
// TODO: use Supaglue TS client | ||
export async function updateRemoteIntegration(data: any) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. data can be typed |
||
const result = await fetch(`${API_HOST}/mgmt/v1/applications/${APPLICATION_ID}/integrations/${data.id}`, { | ||
method: 'PUT', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify(snakecaseKeys(data)), | ||
}); | ||
|
||
const r = await result.json(); | ||
return r; | ||
} | ||
|
||
// TODO: use Supaglue TS client | ||
export async function createRemoteIntegration(data: any) { | ||
const result = await fetch(`${API_HOST}/mgmt/v1/applications/${APPLICATION_ID}/integrations`, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify(snakecaseKeys(data)), | ||
}); | ||
|
||
const r = await result.json(); | ||
return r; | ||
} | ||
|
||
// TODO: use Supaglue TS client | ||
export async function removeRemoteIntegration(data: any) { | ||
const result = await fetch(`${API_HOST}/mgmt/v1/applications/${APPLICATION_ID}/integrations/${data.id}`, { | ||
method: 'DELETE', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
}); | ||
|
||
const r = await result.json(); | ||
return r; | ||
} | ||
|
||
// TODO: add other calls |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,19 @@ | ||
/* eslint-disable @typescript-eslint/no-floating-promises */ | ||
import { sendRequest } from '@/sendRequests'; | ||
import { Button, Card, CardContent, CardHeader, Divider, Grid, Switch } from '@mui/material'; | ||
import { useIntegration } from '@/hooks/useIntegration'; | ||
import { useIntegrations } from '@/hooks/useIntegrations'; | ||
import { Button, Card, CardContent, CardHeader, Divider, Grid, Stack, Switch, Typography } from '@mui/material'; | ||
import { Box } from '@mui/system'; | ||
import { useRouter } from 'next/router'; | ||
import useSWRMutation from 'swr/mutation'; | ||
import { Integration, IntegrationCardInfo } from './VerticalTabs'; | ||
|
||
export default function IntegrationCard(props: { | ||
integration: Integration; | ||
integrationInfo: IntegrationCardInfo; | ||
enabled: boolean; | ||
}) { | ||
export default function IntegrationCard(props: { integration: Integration; integrationInfo: IntegrationCardInfo }) { | ||
const router = useRouter(); | ||
const { trigger } = useSWRMutation('/mgmt/v1/integrations', sendRequest); | ||
const { enabled, integration } = props; | ||
const { icon, name, description, category, providerName } = props.integrationInfo; | ||
const { integration } = props; | ||
const { integrations: existingIntegrations = [], mutate } = useIntegrations(); | ||
const { mutate: mutateIntegration } = useIntegration(integration?.id); // TODO: run this when there's an integration only | ||
|
||
const { icon, name, description, category, status, providerName } = props.integrationInfo; | ||
|
||
return ( | ||
<Card | ||
classes={{ | ||
|
@@ -24,13 +23,40 @@ export default function IntegrationCard(props: { | |
<Box> | ||
<CardHeader | ||
avatar={icon} | ||
subheader={name} | ||
subheader={ | ||
<Stack direction="column"> | ||
<Typography>{name}</Typography> | ||
<Typography fontSize={12}>{status === 'auth-only' ? status : category.toUpperCase()}</Typography> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add in integration |
||
</Stack> | ||
} | ||
action={ | ||
<Switch | ||
checked={enabled} | ||
onClick={() => { | ||
trigger({ ...integration, enabled: !enabled }); | ||
}} | ||
disabled={true} | ||
checked={integration?.isEnabled} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. data-drive |
||
// onClick={() => { | ||
// if (!integration) { | ||
// const newIntegration = { | ||
// authType: 'oauth2', | ||
// category, | ||
// providerName, | ||
// isEnabled: true, // TODO: we need another notion of live vs enabled | ||
// applicationId: APPLICATION_ID, | ||
// }; | ||
// const updatedIntegrations = [...existingIntegrations, newIntegration]; | ||
|
||
// mutate(updatedIntegrations, false); | ||
// mutateIntegration(createRemoteIntegration(newIntegration), false); | ||
// return; | ||
// } | ||
|
||
// const updatedIntegration = { ...integration, isEnabled: !integration?.isEnabled }; | ||
// const updatedIntegrations = existingIntegrations.map((ei: Integration) => | ||
// ei.id === updatedIntegration.id ? updatedIntegration : ei | ||
// ); | ||
|
||
// mutate(updatedIntegrations, false); | ||
// mutateIntegration(updateRemoteIntegration(updatedIntegration), false); | ||
// }} | ||
></Switch> | ||
} | ||
/> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
/* eslint-disable @typescript-eslint/no-floating-promises */ | ||
import { sendRequest } from '@/sendRequests'; | ||
import { removeRemoteIntegration, updateRemoteIntegration } from '@/client'; | ||
import { useIntegration } from '@/hooks/useIntegration'; | ||
import { useIntegrations } from '@/hooks/useIntegrations'; | ||
import providerToIcon from '@/utils/providerToIcon'; | ||
import { Box, Button, Stack, Switch, TextField, Typography } from '@mui/material'; | ||
import { useRouter } from 'next/router'; | ||
import { useEffect, useState } from 'react'; | ||
import useSWRMutation from 'swr/mutation'; | ||
import { Integration, IntegrationCardInfo } from './VerticalTabs'; | ||
|
||
export type IntegrationDetailTabPanelProps = { | ||
|
@@ -17,8 +19,10 @@ export default function IntegrationDetailTabPanel(props: IntegrationDetailTabPan | |
const [clientId, setClientId] = useState(''); | ||
const [clientSecret, setClientSecret] = useState(''); | ||
const [oauthScopes, setOauthScopes] = useState(''); | ||
const router = useRouter(); | ||
|
||
const { trigger } = useSWRMutation('/mgmt/v1/integrations', sendRequest); | ||
const { mutate: mutateIntegration } = useIntegration(integration?.id); | ||
const { integrations: existingIntegrations = [], mutate } = useIntegrations(); | ||
|
||
useEffect(() => { | ||
setClientId(integration?.config?.oauth?.credentials?.oauthClientId); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like integration is already passed in as a prop -- why do we need to do this? |
||
|
@@ -29,12 +33,19 @@ export default function IntegrationDetailTabPanel(props: IntegrationDetailTabPan | |
return ( | ||
<Stack direction="column" className="gap-4"> | ||
<Stack direction="row" className="items-center justify-between w-full"> | ||
<Stack direction="row"> | ||
{providerToIcon(integrationCardInfo.providerName)} | ||
<Typography variant="subtitle1">{integrationCardInfo.name}</Typography> | ||
<Stack direction="row" className="items-center justify-center gap-2"> | ||
{providerToIcon(integrationCardInfo.providerName, 35)} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ICON_SIZE? |
||
<Stack direction="column"> | ||
<Typography variant="subtitle1">{integrationCardInfo.name}</Typography> | ||
<Typography fontSize={12}> | ||
{integrationCardInfo.status === 'auth-only' | ||
? integrationCardInfo.status | ||
: integrationCardInfo.category.toUpperCase()} | ||
</Typography> | ||
</Stack> | ||
</Stack> | ||
<Box> | ||
<Switch></Switch> | ||
<Switch checked={integration?.isEnabled}></Switch> | ||
</Box> | ||
</Stack> | ||
|
||
|
@@ -72,29 +83,62 @@ export default function IntegrationDetailTabPanel(props: IntegrationDetailTabPan | |
}} | ||
/> | ||
</Stack> | ||
<Stack direction="row" className="gap-2"> | ||
<Button variant="outlined">Cancel</Button>{' '} | ||
<Button | ||
variant="contained" | ||
onClick={() => { | ||
trigger({ | ||
...integration, | ||
config: { | ||
...integration?.config, | ||
oauth: { | ||
credentials: { | ||
oauthClientId: clientId, | ||
oauthClientSecret: clientSecret, | ||
<Stack direction="row" className="gap-2 justify-between"> | ||
<Stack direction="row" className="gap-2"> | ||
<Button | ||
variant="outlined" | ||
onClick={() => { | ||
router.back(); | ||
}} | ||
> | ||
Cancel | ||
</Button> | ||
<Button | ||
variant="contained" | ||
onClick={() => { | ||
const newIntegration = { | ||
...integration, | ||
config: { | ||
providerAppId: '', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this line necessary? Does it get overridden by the next line? |
||
...integration?.config, | ||
oauth: { | ||
...integration?.config?.oauth, | ||
credentials: { | ||
oauthClientId: clientId, | ||
oauthClientSecret: clientSecret, | ||
}, | ||
oauthScopes: oauthScopes.split(','), | ||
}, | ||
sync: { | ||
periodMs: 60 * 60 * 1000, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is 1x/hour what we want? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now |
||
}, | ||
oauthScopes: oauthScopes.split(','), | ||
...integration?.config?.oauth, | ||
}, | ||
}, | ||
}); | ||
}} | ||
> | ||
Save | ||
</Button> | ||
}; | ||
const updatedIntegrations = existingIntegrations.map((ei: Integration) => | ||
ei.id === newIntegration.id ? newIntegration : ei | ||
); | ||
|
||
mutate(updatedIntegrations, false); | ||
mutateIntegration(updateRemoteIntegration(newIntegration), false); | ||
}} | ||
> | ||
Save | ||
</Button> | ||
</Stack> | ||
<Stack direction="row" className="gap-2"> | ||
<Button | ||
variant="text" | ||
color="error" | ||
onClick={() => { | ||
const updatedIntegrations = existingIntegrations.filter((ei: Integration) => ei.id !== integration.id); | ||
mutate(updatedIntegrations, false); | ||
mutateIntegration(removeRemoteIntegration(integration), false); | ||
router.back(); | ||
}} | ||
> | ||
Delete | ||
</Button> | ||
</Stack> | ||
</Stack> | ||
</Stack> | ||
); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of this file can be DRYed