Skip to content

Commit

Permalink
feat(#31): added multi env support in integration
Browse files Browse the repository at this point in the history
  • Loading branch information
akhilmhdh committed Jan 13, 2023
1 parent b67abf9 commit 036d32a
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 83 deletions.
164 changes: 92 additions & 72 deletions frontend/components/integrations/Integration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,24 @@ interface IntegrationApp {
siteId: string;
}

const Integration = ({
integration
}: {
type Props = {
integration: Integration;
}) => {
const [integrationEnvironment, setIntegrationEnvironment] = useState(integration.environment);
environments: Array<{ name: string; slug: string }>;
};

const Integration = ({
integration,
environments = []
}:Props ) => {
// set initial environment. This find will only execute when component is mounting
const [integrationEnvironment, setIntegrationEnvironment] = useState<
Props['environments'][0]
>(
environments.find(({ slug }) => slug === integration.environment) || {
name: '',
slug: '',
}
);
const [fileState, setFileState] = useState([]);
const router = useRouter();
const [apps, setApps] = useState<IntegrationApp[]>([]); // integration app objects
Expand Down Expand Up @@ -132,42 +144,47 @@ const Integration = ({
if (!integrationApp || apps.length === 0) return <div></div>

return (
<div className="max-w-5xl p-6 mx-6 mb-8 rounded-md bg-white/5 flex justify-between">
<div className="flex">
<div className='max-w-5xl p-6 mx-6 mb-8 rounded-md bg-white/5 flex justify-between'>
<div className='flex'>
<div>
<p className="text-gray-400 text-xs font-semibold mb-2">ENVIRONMENT</p>
<ListBox data={!integration.isActive ? [
"Development",
"Staging",
"Testing",
"Production",
] : null}
selected={integrationEnvironment}
onChange={(environment) => {
setIntegrationEnvironment(environment);
}}
<p className='text-gray-400 text-xs font-semibold mb-2'>
ENVIRONMENT
</p>
<ListBox
data={
!integration.isActive
? environments.map(({ name }) => name)
: null
}
selected={integrationEnvironment.name}
onChange={(envName) =>
setIntegrationEnvironment(
environments.find(({ name }) => envName === name) || {
name: 'unknown',
slug: 'unknown',
}
)
}
isFull={true}
/>
</div>
<div className="pt-2">
<div className='pt-2'>
<FontAwesomeIcon
icon={faArrowRight}
className="mx-4 text-gray-400 mt-8"
/>
className='mx-4 text-gray-400 mt-8'
/>
</div>
<div className="mr-2">
<p className="text-gray-400 text-xs font-semibold mb-2">
<div className='mr-2'>
<p className='text-gray-400 text-xs font-semibold mb-2'>
INTEGRATION
</p>
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-10 text-sm font-semibold text-gray-300">
<div className='py-2.5 bg-white/[.07] rounded-md pl-4 pr-10 text-sm font-semibold text-gray-300'>
{integration.integration.charAt(0).toUpperCase() +
integration.integration.slice(1)}
</div>
</div>
<div className="mr-2">
<div className="text-gray-400 text-xs font-semibold mb-2">
APP
</div>
<div className='mr-2'>
<div className='text-gray-400 text-xs font-semibold mb-2'>APP</div>
<ListBox
data={!integration.isActive ? apps.map((app) => app.name) : null}
selected={integrationApp}
Expand All @@ -178,52 +195,55 @@ const Integration = ({
</div>
{renderIntegrationSpecificParams(integration)}
</div>
<div className="flex items-end">
{integration.isActive ? (
<div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
<FontAwesomeIcon
icon={faRotate}
className="text-lg mr-2.5 text-primary animate-spin"
/>
<div className="text-gray-300 font-semibold">In Sync</div>
</div>
) : (
<Button
text="Start Integration"
onButtonPressed={async () => {

const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
const siteId = siteApp?.siteId ? siteApp.siteId : null;

await updateIntegration({
integrationId: integration._id,
environment: integrationEnvironment,
app: integrationApp,
isActive: true,
target: integrationTarget ? integrationTarget.toLowerCase() : null,
context: integrationContext ? reverseContextNetlifyMapping[integrationContext] : null,
siteId
});

router.reload();
}}
color="mineshaft"
size="md"
/>
)}
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
<Button
onButtonPressed={async () => {
await deleteIntegration({
integrationId: integration._id,
});
router.reload();
}}
color="red"
size="icon-md"
icon={faX}
<div className='flex items-end'>
{integration.isActive ? (
<div className='max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4'>
<FontAwesomeIcon
icon={faRotate}
className='text-lg mr-2.5 text-primary animate-spin'
/>
<div className='text-gray-300 font-semibold'>In Sync</div>
</div>
) : (
<Button
text='Start Integration'
onButtonPressed={async () => {
const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
const siteId = siteApp?.siteId ? siteApp.siteId : null;

await updateIntegration({
integrationId: integration._id,
environment: integrationEnvironment.slug,
app: integrationApp,
isActive: true,
target: integrationTarget
? integrationTarget.toLowerCase()
: null,
context: integrationContext
? reverseContextNetlifyMapping[integrationContext]
: null,
siteId,
});

router.reload();
}}
color='mineshaft'
size='md'
/>
)}
<div className='opacity-50 hover:opacity-100 duration-200 ml-2'>
<Button
onButtonPressed={async () => {
await deleteIntegration({
integrationId: integration._id,
});
router.reload();
}}
color='red'
size='icon-md'
icon={faX}
/>
</div>
</div>
</div>
);
Expand Down
7 changes: 5 additions & 2 deletions frontend/components/integrations/IntegrationSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import guidGenerator from "~/utilities/randomId";
import Integration from "./Integration";

interface Props {
integrations: any
integrations: any;
environments: Array<{ name: string; slug: string }>;
}

interface IntegrationType {
Expand All @@ -19,7 +20,8 @@ interface IntegrationType {
}

const ProjectIntegrationSection = ({
integrations
integrations,
environments = [],
}: Props) => {
return integrations.length > 0 ? (
<div className="mb-12">
Expand All @@ -33,6 +35,7 @@ const ProjectIntegrationSection = ({
<Integration
key={guidGenerator()}
integration={integration}
environments={environments}
/>
))}
</div>
Expand Down
31 changes: 31 additions & 0 deletions frontend/pages/api/workspace/getAWorkspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import SecurityClient from '~/utilities/SecurityClient';

interface Workspace {
__v: number;
_id: string;
name: string;
organization: string;
environments: Array<{ name: string; slug: string }>;
}

/**
* This route lets us get the workspaces of a certain user
* @returns
*/
const getAWorkspace = (workspaceID:string) => {
return SecurityClient.fetchCall(`/api/v1/workspace/${workspaceID}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}).then(async (res) => {
if (res?.status == 200) {
const data = (await res.json()) as unknown as { workspace: Workspace };
return data.workspace;
}

throw new Error('Failed to get workspace');
});
};

export default getAWorkspace;
20 changes: 11 additions & 9 deletions frontend/pages/integrations/[id].js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import setBotActiveStatus from "../api/bot/setBotActiveStatus";
import getIntegrationOptions from "../api/integrations/GetIntegrationOptions";
import getWorkspaceAuthorizations from "../api/integrations/getWorkspaceAuthorizations";
import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations";
import getAWorkspace from "../api/workspace/getAWorkspace";
import getLatestFileKey from "../api/workspace/getLatestFileKey";
const {
decryptAssymmetric,
Expand All @@ -34,18 +35,23 @@ const crypto = require("crypto");
export default function Integrations() {
const [cloudIntegrationOptions, setCloudIntegrationOptions] = useState([]);
const [integrationAuths, setIntegrationAuths] = useState([]);
const [environments,setEnvironments] = useState([])
const [integrations, setIntegrations] = useState([]);
const [bot, setBot] = useState(null);
const [isActivateBotDialogOpen, setIsActivateBotDialogOpen] = useState(false);
// const [isIntegrationAccessTokenDialogOpen, setIntegrationAccessTokenDialogOpen] = useState(true);
const [selectedIntegrationOption, setSelectedIntegrationOption] = useState(null);

const router = useRouter();
const workspaceId = router.query.id;

const { t } = useTranslation();

useEffect(async () => {
try {
const workspace = await getAWorkspace(workspaceId);
setEnvironments(workspace.environments);

// get cloud integration options
setCloudIntegrationOptions(
await getIntegrationOptions()
Expand All @@ -54,23 +60,19 @@ export default function Integrations() {
// get project integration authorizations
setIntegrationAuths(
await getWorkspaceAuthorizations({
workspaceId: router.query.id,
workspaceId
})
);

// get project integrations
setIntegrations(
await getWorkspaceIntegrations({
workspaceId: router.query.id,
workspaceId,
})
);

// get project bot
setBot(
await getBot({
workspaceId: router.query.id
}
));
setBot(await getBot({ workspaceId }));

} catch (err) {
console.log(err);
Expand All @@ -90,7 +92,7 @@ export default function Integrations() {

if (bot) {
// case: there is a bot
const key = await getLatestFileKey({ workspaceId: router.query.id });
const key = await getLatestFileKey({ workspaceId });
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');

const WORKSPACE_KEY = decryptAssymmetric({
Expand Down Expand Up @@ -214,7 +216,7 @@ export default function Integrations() {
handleBotActivate={handleBotActivate}
handleIntegrationOption={handleIntegrationOption}
/> */}
<IntegrationSection integrations={integrations} />
<IntegrationSection integrations={integrations} environments={environments} />
{(cloudIntegrationOptions.length > 0 && bot) ? (
<CloudIntegrationSection
cloudIntegrationOptions={cloudIntegrationOptions}
Expand Down

0 comments on commit 036d32a

Please sign in to comment.