forked from github/plane
refactor: workspace integrations code (#872)
This commit is contained in:
parent
682a1477fb
commit
1627a587ee
@ -1,51 +1,25 @@
|
|||||||
import { FC, useRef, useState } from "react";
|
// hooks
|
||||||
|
import useIntegrationPopup from "hooks/use-integration-popup";
|
||||||
// ui
|
// ui
|
||||||
import { PrimaryButton } from "components/ui";
|
import { PrimaryButton } from "components/ui";
|
||||||
|
// types
|
||||||
|
import { IWorkspaceIntegration } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string | undefined;
|
workspaceIntegration: false | IWorkspaceIntegration | undefined;
|
||||||
workspaceIntegration: any;
|
provider: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GithubAuth: FC<Props> = ({ workspaceSlug, workspaceIntegration }) => {
|
export const GithubAuth: React.FC<Props> = ({ workspaceIntegration, provider }) => {
|
||||||
const popup = useRef<any>();
|
const { startAuth, isConnecting } = useIntegrationPopup(provider);
|
||||||
const [authLoader, setAuthLoader] = useState(false);
|
|
||||||
|
|
||||||
const checkPopup = () => {
|
|
||||||
const check = setInterval(() => {
|
|
||||||
if (!popup || popup.current.closed || popup.current.closed === undefined) {
|
|
||||||
clearInterval(check);
|
|
||||||
setAuthLoader(false);
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
const openPopup = () => {
|
|
||||||
const width = 600,
|
|
||||||
height = 600;
|
|
||||||
const left = window.innerWidth / 2 - width / 2;
|
|
||||||
const top = window.innerHeight / 2 - height / 2;
|
|
||||||
const url = `https://github.com/apps/${
|
|
||||||
process.env.NEXT_PUBLIC_GITHUB_APP_NAME
|
|
||||||
}/installations/new?state=${workspaceSlug as string}`;
|
|
||||||
|
|
||||||
return window.open(url, "", `width=${width}, height=${height}, top=${top}, left=${left}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const startAuth = () => {
|
|
||||||
popup.current = openPopup();
|
|
||||||
checkPopup();
|
|
||||||
setAuthLoader(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{workspaceIntegration && workspaceIntegration?.id ? (
|
{workspaceIntegration && workspaceIntegration?.id ? (
|
||||||
<PrimaryButton disabled>Successfully Connected</PrimaryButton>
|
<PrimaryButton disabled>Successfully Connected</PrimaryButton>
|
||||||
) : (
|
) : (
|
||||||
<PrimaryButton onClick={startAuth} loading={authLoader}>
|
<PrimaryButton onClick={startAuth} loading={isConnecting}>
|
||||||
{authLoader ? "Connecting..." : "Connect"}
|
{isConnecting ? "Connecting..." : "Connect"}
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,30 +1,23 @@
|
|||||||
import { FC } from "react";
|
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import { GithubAuth, TIntegrationSteps } from "components/integration";
|
import { GithubAuth, TIntegrationSteps } from "components/integration";
|
||||||
// ui
|
// ui
|
||||||
import { PrimaryButton } from "components/ui";
|
import { PrimaryButton } from "components/ui";
|
||||||
// types
|
// types
|
||||||
import { IAppIntegrations, IWorkspaceIntegrations } from "types";
|
import { IAppIntegration, IWorkspaceIntegration } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
provider: string | undefined;
|
provider: string | undefined;
|
||||||
handleStepChange: (value: TIntegrationSteps) => void;
|
handleStepChange: (value: TIntegrationSteps) => void;
|
||||||
appIntegrations: IAppIntegrations[] | undefined;
|
appIntegrations: IAppIntegration[] | undefined;
|
||||||
workspaceIntegrations: IWorkspaceIntegrations[] | undefined;
|
workspaceIntegrations: IWorkspaceIntegration[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GithubImportConfigure: FC<Props> = ({
|
export const GithubImportConfigure: React.FC<Props> = ({
|
||||||
handleStepChange,
|
handleStepChange,
|
||||||
provider,
|
provider,
|
||||||
appIntegrations,
|
appIntegrations,
|
||||||
workspaceIntegrations,
|
workspaceIntegrations,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug } = router.query;
|
|
||||||
|
|
||||||
// current integration from all the integrations available
|
// current integration from all the integrations available
|
||||||
const integration =
|
const integration =
|
||||||
appIntegrations &&
|
appIntegrations &&
|
||||||
@ -46,10 +39,7 @@ export const GithubImportConfigure: FC<Props> = ({
|
|||||||
<div className="text-sm text-gray-600">Set up your GitHub import.</div>
|
<div className="text-sm text-gray-600">Set up your GitHub import.</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<GithubAuth
|
<GithubAuth workspaceIntegration={workspaceIntegration} provider={provider} />
|
||||||
workspaceSlug={workspaceSlug as string}
|
|
||||||
workspaceIntegration={workspaceIntegration}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -11,11 +11,11 @@ import { CustomSearchSelect, PrimaryButton, SecondaryButton } from "components/u
|
|||||||
// helpers
|
// helpers
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IWorkspaceIntegrations } from "types";
|
import { IWorkspaceIntegration } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleStepChange: (value: TIntegrationSteps) => void;
|
handleStepChange: (value: TIntegrationSteps) => void;
|
||||||
integration: IWorkspaceIntegrations | false | undefined;
|
integration: IWorkspaceIntegration | false | undefined;
|
||||||
control: Control<TFormValues, any>;
|
control: Control<TFormValues, any>;
|
||||||
watch: UseFormWatch<TFormValues>;
|
watch: UseFormWatch<TFormValues>;
|
||||||
};
|
};
|
||||||
|
@ -11,10 +11,10 @@ import { CustomSearchSelect } from "components/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IWorkspaceIntegrations } from "types";
|
import { IWorkspaceIntegration } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
integration: IWorkspaceIntegrations;
|
integration: IWorkspaceIntegration;
|
||||||
value: any;
|
value: any;
|
||||||
label: string;
|
label: string;
|
||||||
onChange: (repo: any) => void;
|
onChange: (repo: any) => void;
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
export * from "./delete-import-modal";
|
export * from "./delete-import-modal";
|
||||||
export * from "./guide";
|
export * from "./guide";
|
||||||
export * from "./single-import";
|
export * from "./single-import";
|
||||||
|
export * from "./single-integration-card";
|
||||||
|
|
||||||
// github
|
// github
|
||||||
export * from "./github";
|
export * from "./github";
|
||||||
|
|
||||||
// jira
|
// jira
|
||||||
export * from "./jira";
|
export * from "./jira";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useRef, useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
@ -9,49 +9,44 @@ import useSWR, { mutate } from "swr";
|
|||||||
import IntegrationService from "services/integration";
|
import IntegrationService from "services/integration";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
|
import useIntegrationPopup from "hooks/use-integration-popup";
|
||||||
// ui
|
// ui
|
||||||
import { DangerButton, Loader, SecondaryButton } from "components/ui";
|
import { DangerButton, Loader, SecondaryButton } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import GithubLogo from "public/logos/github-square.png";
|
import GithubLogo from "public/services/github.png";
|
||||||
|
import SlackLogo from "public/services/slack.png";
|
||||||
// types
|
// types
|
||||||
import { IWorkspaceIntegrations } from "types";
|
import { IAppIntegration, IWorkspaceIntegration } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
|
import { WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
|
||||||
|
|
||||||
const OAuthPopUp = ({ integration }: any) => {
|
type Props = {
|
||||||
const [deletingIntegration, setDeletingIntegration] = useState(false);
|
integration: IAppIntegration;
|
||||||
|
};
|
||||||
|
|
||||||
const popup = useRef<any>();
|
const integrationDetails: { [key: string]: any } = {
|
||||||
|
github: {
|
||||||
|
logo: GithubLogo,
|
||||||
|
installed:
|
||||||
|
"Activate GitHub integrations on individual projects to sync with specific repositories.",
|
||||||
|
notInstalled: "Connect with GitHub with your Plane workspace to sync project issues.",
|
||||||
|
},
|
||||||
|
slack: {
|
||||||
|
logo: SlackLogo,
|
||||||
|
installed: "Activate Slack integrations on individual projects to sync with specific cahnnels.",
|
||||||
|
notInstalled: "Connect with Slack with your Plane workspace to sync project issues.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SingleIntegrationCard: React.FC<Props> = ({ integration }) => {
|
||||||
|
const [deletingIntegration, setDeletingIntegration] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const checkPopup = () => {
|
const { startAuth, isConnecting: isInstalling } = useIntegrationPopup(integration.provider);
|
||||||
const check = setInterval(() => {
|
|
||||||
if (!popup || popup.current.closed || popup.current.closed === undefined) {
|
|
||||||
clearInterval(check);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
const openPopup = () => {
|
|
||||||
const width = 600,
|
|
||||||
height = 600;
|
|
||||||
const left = window.innerWidth / 2 - width / 2;
|
|
||||||
const top = window.innerHeight / 2 - height / 2;
|
|
||||||
const url = `https://github.com/apps/${
|
|
||||||
process.env.NEXT_PUBLIC_GITHUB_APP_NAME
|
|
||||||
}/installations/new?state=${workspaceSlug as string}`;
|
|
||||||
|
|
||||||
return window.open(url, "", `width=${width}, height=${height}, top=${top}, left=${left}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const startAuth = () => {
|
|
||||||
popup.current = openPopup();
|
|
||||||
checkPopup();
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: workspaceIntegrations } = useSWR(
|
const { data: workspaceIntegrations } = useSWR(
|
||||||
workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null,
|
workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null,
|
||||||
@ -75,7 +70,7 @@ const OAuthPopUp = ({ integration }: any) => {
|
|||||||
workspaceIntegrationId ?? ""
|
workspaceIntegrationId ?? ""
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate<IWorkspaceIntegrations[]>(
|
mutate<IWorkspaceIntegration[]>(
|
||||||
WORKSPACE_INTEGRATIONS(workspaceSlug as string),
|
WORKSPACE_INTEGRATIONS(workspaceSlug as string),
|
||||||
(prevData) => prevData?.filter((i) => i.id !== workspaceIntegrationId),
|
(prevData) => prevData?.filter((i) => i.id !== workspaceIntegrationId),
|
||||||
false
|
false
|
||||||
@ -107,7 +102,10 @@ const OAuthPopUp = ({ integration }: any) => {
|
|||||||
<div className="flex items-center justify-between gap-2 rounded-[10px] border bg-white p-5">
|
<div className="flex items-center justify-between gap-2 rounded-[10px] border bg-white p-5">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="h-12 w-12 flex-shrink-0">
|
<div className="h-12 w-12 flex-shrink-0">
|
||||||
<Image src={GithubLogo} alt="GithubLogo" />
|
<Image
|
||||||
|
src={integrationDetails[integration.provider].logo}
|
||||||
|
alt={`${integration.title} Logo`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="flex items-center gap-4 text-xl font-semibold">
|
<h3 className="flex items-center gap-4 text-xl font-semibold">
|
||||||
@ -128,8 +126,8 @@ const OAuthPopUp = ({ integration }: any) => {
|
|||||||
<p className="text-sm text-gray-400">
|
<p className="text-sm text-gray-400">
|
||||||
{workspaceIntegrations
|
{workspaceIntegrations
|
||||||
? isInstalled
|
? isInstalled
|
||||||
? "Activate GitHub integrations on individual projects to sync with specific repositories."
|
? integrationDetails[integration.provider].installed
|
||||||
: "Connect with GitHub with your Plane workspace to sync project issues."
|
: integrationDetails[integration.provider].notInstalled
|
||||||
: "Loading..."}
|
: "Loading..."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -141,8 +139,8 @@ const OAuthPopUp = ({ integration }: any) => {
|
|||||||
{deletingIntegration ? "Removing..." : "Remove installation"}
|
{deletingIntegration ? "Removing..." : "Remove installation"}
|
||||||
</DangerButton>
|
</DangerButton>
|
||||||
) : (
|
) : (
|
||||||
<SecondaryButton onClick={startAuth} outline>
|
<SecondaryButton onClick={startAuth} loading={isInstalling}>
|
||||||
Add installation
|
{isInstalling ? "Installing..." : "Add installation"}
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
@ -153,5 +151,3 @@ const OAuthPopUp = ({ integration }: any) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OAuthPopUp;
|
|
@ -14,12 +14,12 @@ import { SelectRepository } from "components/integration";
|
|||||||
// icons
|
// icons
|
||||||
import GithubLogo from "public/logos/github-square.png";
|
import GithubLogo from "public/logos/github-square.png";
|
||||||
// types
|
// types
|
||||||
import { IWorkspaceIntegrations } from "types";
|
import { IWorkspaceIntegration } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_GITHUB_REPOSITORY } from "constants/fetch-keys";
|
import { PROJECT_GITHUB_REPOSITORY } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
integration: IWorkspaceIntegrations;
|
integration: IWorkspaceIntegration;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SingleIntegration: React.FC<Props> = ({ integration }) => {
|
export const SingleIntegration: React.FC<Props> = ({ integration }) => {
|
||||||
|
52
apps/app/hooks/use-integration-popup.tsx
Normal file
52
apps/app/hooks/use-integration-popup.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { useRef, useState } from "react";
|
||||||
|
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
const useIntegrationPopup = (provider: string | undefined) => {
|
||||||
|
const [authLoader, setAuthLoader] = useState(false);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
|
const providerUrls: { [key: string]: string } = {
|
||||||
|
github: `https://github.com/apps/${
|
||||||
|
process.env.NEXT_PUBLIC_GITHUB_APP_NAME
|
||||||
|
}/installations/new?state=${workspaceSlug as string}`,
|
||||||
|
slack: "",
|
||||||
|
};
|
||||||
|
const popup = useRef<any>();
|
||||||
|
|
||||||
|
const checkPopup = () => {
|
||||||
|
const check = setInterval(() => {
|
||||||
|
if (!popup || popup.current.closed || popup.current.closed === undefined) {
|
||||||
|
clearInterval(check);
|
||||||
|
setAuthLoader(false);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openPopup = () => {
|
||||||
|
if (!provider) return;
|
||||||
|
|
||||||
|
const width = 600,
|
||||||
|
height = 600;
|
||||||
|
const left = window.innerWidth / 2 - width / 2;
|
||||||
|
const top = window.innerHeight / 2 - height / 2;
|
||||||
|
const url = providerUrls[provider];
|
||||||
|
|
||||||
|
return window.open(url, "", `width=${width}, height=${height}, top=${top}, left=${left}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const startAuth = () => {
|
||||||
|
popup.current = openPopup();
|
||||||
|
checkPopup();
|
||||||
|
setAuthLoader(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
startAuth,
|
||||||
|
isConnecting: authLoader,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useIntegrationPopup;
|
@ -10,7 +10,7 @@ import IntegrationService from "services/integration";
|
|||||||
// layouts
|
// layouts
|
||||||
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
|
||||||
// components
|
// components
|
||||||
import OAuthPopUp from "components/popup";
|
import { SingleIntegrationCard } from "components/integration";
|
||||||
// ui
|
// ui
|
||||||
import { Loader } from "components/ui";
|
import { Loader } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
@ -49,11 +49,7 @@ const WorkspaceIntegrations: NextPage = () => {
|
|||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
{appIntegrations ? (
|
{appIntegrations ? (
|
||||||
appIntegrations.map((integration) => (
|
appIntegrations.map((integration) => (
|
||||||
<OAuthPopUp
|
<SingleIntegrationCard key={integration.id} integration={integration} />
|
||||||
key={integration.id}
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
integration={integration}
|
|
||||||
/>
|
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<Loader className="space-y-5">
|
<Loader className="space-y-5">
|
||||||
|
BIN
apps/app/public/services/slack.png
Normal file
BIN
apps/app/public/services/slack.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
@ -1,6 +1,6 @@
|
|||||||
import APIService from "services/api.service";
|
import APIService from "services/api.service";
|
||||||
// types
|
// types
|
||||||
import { IAppIntegrations, IImporterService, IWorkspaceIntegrations } from "types";
|
import { IAppIntegration, IImporterService, IWorkspaceIntegration } from "types";
|
||||||
|
|
||||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ class IntegrationService extends APIService {
|
|||||||
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAppIntegrationsList(): Promise<IAppIntegrations[]> {
|
async getAppIntegrationsList(): Promise<IAppIntegration[]> {
|
||||||
return this.get(`/api/integrations/`)
|
return this.get(`/api/integrations/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -17,7 +17,7 @@ class IntegrationService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWorkspaceIntegrationsList(workspaceSlug: string): Promise<IWorkspaceIntegrations[]> {
|
async getWorkspaceIntegrationsList(workspaceSlug: string): Promise<IWorkspaceIntegration[]> {
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/workspace-integrations/`)
|
return this.get(`/api/workspaces/${workspaceSlug}/workspace-integrations/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
4
apps/app/types/integration.d.ts
vendored
4
apps/app/types/integration.d.ts
vendored
@ -1,5 +1,5 @@
|
|||||||
// All the app integrations that are available
|
// All the app integrations that are available
|
||||||
export interface IAppIntegrations {
|
export interface IAppIntegration {
|
||||||
author: string;
|
author: string;
|
||||||
author: "";
|
author: "";
|
||||||
avatar_url: string | null;
|
avatar_url: string | null;
|
||||||
@ -19,7 +19,7 @@ export interface IAppIntegrations {
|
|||||||
webhook_url: string;
|
webhook_url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkspaceIntegrations {
|
export interface IWorkspaceIntegration {
|
||||||
actor: string;
|
actor: string;
|
||||||
api_token: string;
|
api_token: string;
|
||||||
config: any;
|
config: any;
|
||||||
|
Loading…
Reference in New Issue
Block a user