From c9d8a8dbd1f130a561396e9f12976ab5f13e15d0 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Thu, 6 Apr 2023 00:51:15 +0530 Subject: [PATCH] feat: github importer (#722) * chore: github importer first step completed * refactor: github importer code refactored * chore: github importer functionality completed * fix: import data step saved data --- .../core/board-view/board-header.tsx | 4 +- .../components/integration/github/auth.tsx | 19 +- .../integration/github/configure.tsx | 74 ---- .../components/integration/github/confirm.tsx | 27 -- .../integration/github/import-configure.tsx | 66 ++++ .../integration/github/import-confirm.tsx | 27 ++ .../integration/github/import-data.tsx | 146 +++++-- .../integration/github/import-users.tsx | 59 +++ .../integration/github/issues-select.tsx | 27 -- .../integration/github/repo-details.tsx | 105 ++++++ .../components/integration/github/root.tsx | 355 +++++++++++------- .../integration/github/select-repository.tsx | 95 +++++ .../integration/github/single-user-select.tsx | 133 +++++++ .../integration/github/users-select.tsx | 27 -- apps/app/components/integration/guide.tsx | 302 ++++++++------- apps/app/components/integration/index.ts | 10 +- apps/app/components/popup/index.tsx | 12 +- .../project/single-integration-card.tsx | 143 +------ .../components/ui/custom-search-select.tsx | 18 +- apps/app/constants/fetch-keys.ts | 14 +- apps/app/layouts/settings-navbar.tsx | 8 +- apps/app/next.config.js | 1 + .../[projectId]/settings/integrations.tsx | 11 +- .../settings/import-export.tsx | 96 ++--- .../[workspaceSlug]/settings/integrations.tsx | 9 +- .../services/integration/github.service.ts | 22 +- apps/app/services/integration/index.ts | 24 +- apps/app/services/workspace.service.ts | 28 -- apps/app/types/importer/github-importer.d.ts | 33 ++ apps/app/types/importer/index.ts | 33 ++ apps/app/types/index.d.ts | 1 + 31 files changed, 1211 insertions(+), 718 deletions(-) delete mode 100644 apps/app/components/integration/github/configure.tsx delete mode 100644 apps/app/components/integration/github/confirm.tsx create mode 100644 apps/app/components/integration/github/import-configure.tsx create mode 100644 apps/app/components/integration/github/import-confirm.tsx create mode 100644 apps/app/components/integration/github/import-users.tsx delete mode 100644 apps/app/components/integration/github/issues-select.tsx create mode 100644 apps/app/components/integration/github/repo-details.tsx create mode 100644 apps/app/components/integration/github/select-repository.tsx create mode 100644 apps/app/components/integration/github/single-user-select.tsx delete mode 100644 apps/app/components/integration/github/users-select.tsx create mode 100644 apps/app/types/importer/github-importer.d.ts create mode 100644 apps/app/types/importer/index.ts diff --git a/apps/app/components/core/board-view/board-header.tsx b/apps/app/components/core/board-view/board-header.tsx index b9aab7bb4..5eaed3a45 100644 --- a/apps/app/components/core/board-view/board-header.tsx +++ b/apps/app/components/core/board-view/board-header.tsx @@ -142,7 +142,9 @@ export const BoardHeader: React.FC = ({ > {getGroupTitle()} - + {groupedByIssues?.[groupTitle].length ?? 0} diff --git a/apps/app/components/integration/github/auth.tsx b/apps/app/components/integration/github/auth.tsx index d65195948..6055cd903 100644 --- a/apps/app/components/integration/github/auth.tsx +++ b/apps/app/components/integration/github/auth.tsx @@ -1,5 +1,8 @@ import { FC, useRef, useState } from "react"; +// ui +import { PrimaryButton } from "components/ui"; + type Props = { workspaceSlug: string | undefined; workspaceIntegration: any; @@ -39,21 +42,11 @@ export const GithubAuth: FC = ({ workspaceSlug, workspaceIntegration }) = return (
{workspaceIntegration && workspaceIntegration?.id ? ( - + Successfully Connected ) : ( - + )}
); diff --git a/apps/app/components/integration/github/configure.tsx b/apps/app/components/integration/github/configure.tsx deleted file mode 100644 index d2a13cfd9..000000000 --- a/apps/app/components/integration/github/configure.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { FC } from "react"; -// components -import { IIntegrationData, GithubAuth } from "components/integration"; -// types -import { IAppIntegrations } from "types"; - -type Props = { - state: IIntegrationData; - provider: string | undefined; - handleState: (key: string, valve: any) => void; - workspaceSlug: string | undefined; - allIntegrations: IAppIntegrations[] | undefined; - allIntegrationsError: Error | undefined; - allWorkspaceIntegrations: any | undefined; - allWorkspaceIntegrationsError: Error | undefined; -}; - -export const GithubConfigure: FC = ({ - state, - handleState, - workspaceSlug, - provider, - allIntegrations, - allIntegrationsError, - allWorkspaceIntegrations, - allWorkspaceIntegrationsError, -}) => { - // current integration from all the integrations available - const integration = - allIntegrations && - allIntegrations.length > 0 && - allIntegrations.find((_integration) => _integration.provider === provider); - - // current integration from workspace integrations - const workspaceIntegration = - integration && - allWorkspaceIntegrations && - allWorkspaceIntegrations.length > 0 && - allWorkspaceIntegrations.find( - (_integration: any) => _integration.integration_detail.id === integration.id - ); - - console.log("integration", integration); - console.log("workspaceIntegration", workspaceIntegration); - - return ( -
-
-
-
Configure
-
Set up your Github import
-
-
- -
-
- -
- -
-
- ); -}; diff --git a/apps/app/components/integration/github/confirm.tsx b/apps/app/components/integration/github/confirm.tsx deleted file mode 100644 index ceca1ef6c..000000000 --- a/apps/app/components/integration/github/confirm.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { FC } from "react"; -// types -import { IIntegrationData } from "components/integration"; - -type Props = { state: IIntegrationData; handleState: (key: string, valve: any) => void }; - -export const GithubConfirm: FC = ({ state, handleState }) => ( - <> -
Confirm
-
- - -
- -); diff --git a/apps/app/components/integration/github/import-configure.tsx b/apps/app/components/integration/github/import-configure.tsx new file mode 100644 index 000000000..ecae21eae --- /dev/null +++ b/apps/app/components/integration/github/import-configure.tsx @@ -0,0 +1,66 @@ +import { FC } from "react"; + +import { useRouter } from "next/router"; + +// components +import { GithubAuth, TIntegrationSteps } from "components/integration"; +// ui +import { PrimaryButton } from "components/ui"; +// types +import { IAppIntegrations, IWorkspaceIntegrations } from "types"; + +type Props = { + provider: string | undefined; + handleStepChange: (value: TIntegrationSteps) => void; + appIntegrations: IAppIntegrations[] | undefined; + workspaceIntegrations: IWorkspaceIntegrations[] | undefined; +}; + +export const GithubImportConfigure: FC = ({ + handleStepChange, + provider, + appIntegrations, + workspaceIntegrations, +}) => { + const router = useRouter(); + const { workspaceSlug } = router.query; + + // current integration from all the integrations available + const integration = + appIntegrations && + appIntegrations.length > 0 && + appIntegrations.find((i) => i.provider === provider); + + // current integration from workspace integrations + const workspaceIntegration = + integration && + workspaceIntegrations && + workspaceIntegrations.length > 0 && + workspaceIntegrations.find((i: any) => i.integration_detail.id === integration.id); + + return ( +
+
+
+
Configure
+
Set up your GitHub import.
+
+
+ +
+
+ +
+ handleStepChange("import-data")} + disabled={workspaceIntegration && workspaceIntegration?.id ? false : true} + > + Next + +
+
+ ); +}; diff --git a/apps/app/components/integration/github/import-confirm.tsx b/apps/app/components/integration/github/import-confirm.tsx new file mode 100644 index 000000000..3e5ed5808 --- /dev/null +++ b/apps/app/components/integration/github/import-confirm.tsx @@ -0,0 +1,27 @@ +import { FC } from "react"; + +// react-hook-form +import { UseFormWatch } from "react-hook-form"; +// ui +import { PrimaryButton, SecondaryButton } from "components/ui"; +// types +import { TFormValues, TIntegrationSteps } from "components/integration"; + +type Props = { + handleStepChange: (value: TIntegrationSteps) => void; + watch: UseFormWatch; +}; + +export const GithubImportConfirm: FC = ({ handleStepChange, watch }) => ( +
+

+ You are about to import issues from {watch("github").full_name}. Click on {'"'}Confirm & + Import{'" '} + to complete the process. +

+
+ handleStepChange("import-users")}>Back + Confirm & Import +
+
+); diff --git a/apps/app/components/integration/github/import-data.tsx b/apps/app/components/integration/github/import-data.tsx index c98797cec..c3fa871e3 100644 --- a/apps/app/components/integration/github/import-data.tsx +++ b/apps/app/components/integration/github/import-data.tsx @@ -1,27 +1,127 @@ -import { FC } from "react"; +import { FC, useState } from "react"; + +// react-hook-form +import { Control, Controller, UseFormWatch } from "react-hook-form"; +// hooks +import useProjects from "hooks/use-projects"; +// components +import { SelectRepository, TFormValues, TIntegrationSteps } from "components/integration"; +// ui +import { CustomSearchSelect, PrimaryButton, SecondaryButton } from "components/ui"; +// helpers +import { truncateText } from "helpers/string.helper"; // types -import { IIntegrationData } from "components/integration"; +import { IWorkspaceIntegrations } from "types"; -type Props = { state: IIntegrationData; handleState: (key: string, valve: any) => void }; +type Props = { + handleStepChange: (value: TIntegrationSteps) => void; + integration: IWorkspaceIntegrations | false | undefined; + control: Control; + watch: UseFormWatch; +}; -export const GithubImportData: FC = ({ state, handleState }) => ( -
-
Import Data
-
- - +export const GithubImportData: FC = ({ handleStepChange, integration, control, watch }) => { + const { projects } = useProjects(); + + const options = + projects.map((project) => ({ + value: project.id, + query: project.name, + content:

{truncateText(project.name, 25)}

, + })) ?? []; + + return ( +
+
+
+
+

Select Repository

+

+ Select the repository that you want the issues to be imported from. +

+
+
+ {integration && ( + ( + + )} + /> + )} +
+
+
+
+

Select Project

+

Select the project to import the issues to.

+
+
+ {projects && ( + ( + p.id === value)?.name : "Select Project"} + onChange={onChange} + options={options} + optionsClassName="w-full" + /> + )} + /> + )} +
+
+
+
+

Sync Issues

+

Set whether you want to sync the issues or not.

+
+
+ ( + + )} + /> +
+
+
+
+ handleStepChange("import-configure")}>Back + handleStepChange("repo-details")} + disabled={!watch("github") || !watch("project")} + > + Next + +
-
-); + ); +}; diff --git a/apps/app/components/integration/github/import-users.tsx b/apps/app/components/integration/github/import-users.tsx new file mode 100644 index 000000000..e2bd0e153 --- /dev/null +++ b/apps/app/components/integration/github/import-users.tsx @@ -0,0 +1,59 @@ +import { FC } from "react"; + +import { useRouter } from "next/router"; + +// react-hook-form +import { UseFormWatch } from "react-hook-form"; +// ui +import { PrimaryButton, SecondaryButton } from "components/ui"; +// types +import { + IUserDetails, + SingleUserSelect, + TFormValues, + TIntegrationSteps, +} from "components/integration"; + +type Props = { + handleStepChange: (value: TIntegrationSteps) => void; + users: IUserDetails[]; + setUsers: React.Dispatch>; + watch: UseFormWatch; +}; + +export const GithubImportUsers: FC = ({ handleStepChange, users, setUsers, watch }) => { + const router = useRouter(); + + const isInvalid = users.filter((u) => u.import !== false && u.email === "").length > 0; + + return ( +
+
+
+
Name
+
Import as...
+
+ {users.filter((u) => u.import !== false).length} users selected +
+
+
+ {watch("collaborators").map((collaborator, index) => ( + + ))} +
+
+
+ handleStepChange("repo-details")}>Back + handleStepChange("import-confirm")} disabled={isInvalid}> + Next + +
+
+ ); +}; diff --git a/apps/app/components/integration/github/issues-select.tsx b/apps/app/components/integration/github/issues-select.tsx deleted file mode 100644 index 82decd8fc..000000000 --- a/apps/app/components/integration/github/issues-select.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { FC } from "react"; -// types -import { IIntegrationData } from "components/integration"; - -type Props = { state: IIntegrationData; handleState: (key: string, valve: any) => void }; - -export const GithubIssuesSelect: FC = ({ state, handleState }) => ( -
-
Issues Select
-
- - -
-
-); diff --git a/apps/app/components/integration/github/repo-details.tsx b/apps/app/components/integration/github/repo-details.tsx new file mode 100644 index 000000000..61ca8dbb2 --- /dev/null +++ b/apps/app/components/integration/github/repo-details.tsx @@ -0,0 +1,105 @@ +import { FC, useEffect } from "react"; + +import { useRouter } from "next/router"; + +import useSWR from "swr"; + +// react-hook-form +import { UseFormSetValue } from "react-hook-form"; +// services +import GithubIntegrationService from "services/integration/github.service"; +// ui +import { Loader, PrimaryButton, SecondaryButton } from "components/ui"; +// types +import { IUserDetails, TFormValues, TIntegrationSteps } from "components/integration"; +// fetch-keys +import { GITHUB_REPOSITORY_INFO } from "constants/fetch-keys"; + +type Props = { + selectedRepo: any; + handleStepChange: (value: TIntegrationSteps) => void; + setUsers: React.Dispatch>; + setValue: UseFormSetValue; +}; + +export const GithubRepoDetails: FC = ({ + selectedRepo, + handleStepChange, + setUsers, + setValue, +}) => { + const router = useRouter(); + const { workspaceSlug } = router.query; + + const { data: repoInfo } = useSWR( + workspaceSlug && selectedRepo + ? GITHUB_REPOSITORY_INFO(workspaceSlug as string, selectedRepo.name) + : null, + workspaceSlug && selectedRepo + ? () => + GithubIntegrationService.getGithubRepoInfo(workspaceSlug as string, { + owner: selectedRepo.owner.login, + repo: selectedRepo.name, + }) + : null + ); + + useEffect(() => { + if (!repoInfo) return; + + setValue("collaborators", repoInfo.collaborators); + + const fetchedUsers = repoInfo.collaborators.map((collaborator) => ({ + username: collaborator.login, + import: "map", + email: "", + })); + setUsers(fetchedUsers); + }, [repoInfo, setUsers, setValue]); + + return ( +
+ {repoInfo ? ( + repoInfo.issue_count > 0 ? ( +
+
+
Repository Details
+
Import completed. We have found:
+
+
+
+

{repoInfo.issue_count}

+
Issues
+
+
+

{repoInfo.labels}

+
Labels
+
+
+

{repoInfo.collaborators.length}

+
Users
+
+
+
+ ) : ( +
+
We didn{"'"}t find any issue in this repository.
+
+ ) + ) : ( + + + + )} +
+ handleStepChange("import-data")}>Back + handleStepChange("import-users")} + disabled={!repoInfo || repoInfo.issue_count === 0} + > + Next + +
+
+ ); +}; diff --git a/apps/app/components/integration/github/root.tsx b/apps/app/components/integration/github/root.tsx index cc30ba0df..f5ace7909 100644 --- a/apps/app/components/integration/github/root.tsx +++ b/apps/app/components/integration/github/root.tsx @@ -1,168 +1,271 @@ import { FC, useState } from "react"; -// next imports + import Link from "next/link"; import Image from "next/image"; -// icons -import GithubLogo from "public/logos/github-square.png"; -import { CogIcon, CloudUploadIcon, UsersIcon, ImportLayersIcon, CheckIcon } from "components/icons"; -import { ArrowLeftIcon } from "@heroicons/react/24/outline"; +import { useRouter } from "next/router"; + +import { mutate } from "swr"; + +// react-hook-form +import { useForm } from "react-hook-form"; +// services +import GithubIntegrationService from "services/integration/github.service"; +// hooks +import useToast from "hooks/use-toast"; // components import { - GithubConfigure, + GithubImportConfigure, GithubImportData, - GithubIssuesSelect, - GithubUsersSelect, - GithubConfirm, + GithubRepoDetails, + GithubImportUsers, + GithubImportConfirm, } from "components/integration"; +// icons +import { CogIcon, CloudUploadIcon, UsersIcon, CheckIcon } from "components/icons"; +import { ArrowLeftIcon, ListBulletIcon } from "@heroicons/react/24/outline"; +// images +import GithubLogo from "public/logos/github-square.png"; // types -import { IAppIntegrations } from "types"; +import { + IAppIntegrations, + IGithubRepoCollaborator, + IGithubServiceImportFormData, + IWorkspaceIntegrations, +} from "types"; +// fetch-keys +import { IMPORTER_SERVICES_LIST } from "constants/fetch-keys"; type Props = { - workspaceSlug: string | undefined; provider: string | undefined; - allIntegrations: IAppIntegrations[] | undefined; - allIntegrationsError: Error | undefined; - allWorkspaceIntegrations: any | undefined; - allWorkspaceIntegrationsError: Error | undefined; - allIntegrationImporters: any | undefined; - allIntegrationImportersError: Error | undefined; + appIntegrations: IAppIntegrations[] | undefined; + workspaceIntegrations: IWorkspaceIntegrations[] | undefined; }; +export type TIntegrationSteps = + | "import-configure" + | "import-data" + | "repo-details" + | "import-users" + | "import-confirm"; export interface IIntegrationData { - state: string; + state: TIntegrationSteps; } +export interface IUserDetails { + username: string; + import: any; + email: string; +} + +export type TFormValues = { + github: any; + project: string | null; + sync: boolean; + collaborators: IGithubRepoCollaborator[]; + users: IUserDetails[]; +}; + +const defaultFormValues = { + github: null, + project: null, + sync: false, +}; + +const integrationWorkflowData = [ + { + title: "Configure", + key: "import-configure", + icon: CogIcon, + }, + { + title: "Import Data", + key: "import-data", + icon: CloudUploadIcon, + }, + { title: "Issues", key: "repo-details", icon: ListBulletIcon }, + { + title: "Users", + key: "import-users", + icon: UsersIcon, + }, + { + title: "Confirm", + key: "import-confirm", + icon: CheckIcon, + }, +]; + export const GithubIntegrationRoot: FC = ({ - workspaceSlug, provider, - allIntegrations, - allIntegrationsError, - allWorkspaceIntegrations, - allWorkspaceIntegrationsError, - allIntegrationImporters, - allIntegrationImportersError, + appIntegrations, + workspaceIntegrations, }) => { - const integrationWorkflowData = [ - { - title: "Configure", - key: "import-configure", - icon: CogIcon, - }, - { - title: "Import Data", - key: "import-import-data", - icon: CloudUploadIcon, - }, - { title: "Issues", key: "migrate-issues", icon: UsersIcon }, - { - title: "Users", - key: "migrate-users", - icon: ImportLayersIcon, - }, - { - title: "Confirm", - key: "migrate-confirm", - icon: CheckIcon, - }, - ]; + const [currentStep, setCurrentStep] = useState({ + state: "import-configure", + }); + const [users, setUsers] = useState([]); + + const router = useRouter(); + const { workspaceSlug } = router.query; + + const { setToastAlert } = useToast(); + + const { handleSubmit, control, setValue, watch } = useForm({ + defaultValues: defaultFormValues, + }); + const activeIntegrationState = () => { const currentElementIndex = integrationWorkflowData.findIndex( - (_item) => _item?.key === integrationData?.state + (i) => i?.key === currentStep?.state ); return currentElementIndex; }; - const [integrationData, setIntegrationData] = useState({ - state: "import-configure", - }); - const handleIntegrationData = (key: string = "state", value: string) => { - setIntegrationData((previousData) => ({ ...previousData, [key]: value })); + const handleStepChange = (value: TIntegrationSteps) => { + setCurrentStep((prevData) => ({ ...prevData, state: value })); + }; + + // current integration from all the integrations available + const integration = + appIntegrations && + appIntegrations.length > 0 && + appIntegrations.find((i) => i.provider === provider); + + // current integration from workspace integrations + const workspaceIntegration = + integration && + workspaceIntegrations?.find((i: any) => i.integration_detail.id === integration.id); + + const createGithubImporterService = async (formData: TFormValues) => { + if (!formData.github || !formData.project) return; + + const payload: IGithubServiceImportFormData = { + metadata: { + owner: formData.github.owner.login, + name: formData.github.name, + repository_id: formData.github.id, + url: formData.github.html_url, + }, + data: { + users: users, + }, + config: { + sync: formData.sync, + }, + project_id: formData.project, + }; + + await GithubIntegrationService.createGithubServiceImport(workspaceSlug as string, payload) + .then(() => { + router.push(`/${workspaceSlug}/settings/import-export`); + mutate(IMPORTER_SERVICES_LIST(workspaceSlug as string)); + }) + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Import was unsuccessful. Please try again.", + }) + ); }; return ( -
- -
-
- +
+
+ +
+
+ +
+
Cancel import & go back
-
Back
-
- + -
-
-
- GithubLogo -
-
- {integrationWorkflowData.map((_integration, _idx) => ( - <> -
- <_integration.icon - width={`18px`} - height={`18px`} - color={_idx <= activeIntegrationState() ? "#ffffff" : "#d1d5db"} - /> -
- {_idx < integrationWorkflowData.length - 1 && ( +
+
+
+ GithubLogo +
+
+ {integrationWorkflowData.map((integration, index) => ( + <>
- {" "} +
- )} - - ))} + {index < integrationWorkflowData.length - 1 && ( +
+ {" "} +
+ )} + + ))} +
-
-
-
- {integrationData?.state === "import-configure" && ( - - )} - {integrationData?.state === "import-import-data" && ( - - )} - {integrationData?.state === "migrate-issues" && ( - - )} - {integrationData?.state === "migrate-users" && ( - - )} - {integrationData?.state === "migrate-confirm" && ( - - )} +
+
+ {currentStep?.state === "import-configure" && ( + + )} + {currentStep?.state === "import-data" && ( + + )} + {currentStep?.state === "repo-details" && ( + + )} + {currentStep?.state === "import-users" && ( + + )} + {currentStep?.state === "import-confirm" && ( + + )} +
-
+ ); }; diff --git a/apps/app/components/integration/github/select-repository.tsx b/apps/app/components/integration/github/select-repository.tsx new file mode 100644 index 000000000..a306ff0fc --- /dev/null +++ b/apps/app/components/integration/github/select-repository.tsx @@ -0,0 +1,95 @@ +import React from "react"; + +import { useRouter } from "next/router"; + +import useSWRInfinite from "swr/infinite"; + +// services +import projectService from "services/project.service"; +// ui +import { CustomSearchSelect } from "components/ui"; +// helpers +import { truncateText } from "helpers/string.helper"; +// types +import { IWorkspaceIntegrations } from "types"; + +type Props = { + integration: IWorkspaceIntegrations; + value: any; + label: string; + onChange: (repo: any) => void; + characterLimit?: number; +}; + +export const SelectRepository: React.FC = ({ + integration, + value, + label, + onChange, + characterLimit = 25, +}) => { + const router = useRouter(); + const { workspaceSlug } = router.query; + + const getKey = (pageIndex: number) => { + if (!workspaceSlug || !integration) return; + + return `${ + process.env.NEXT_PUBLIC_API_BASE_URL + }/api/workspaces/${workspaceSlug}/workspace-integrations/${ + integration.id + }/github-repositories/?page=${++pageIndex}`; + }; + + const fetchGithubRepos = async (url: string) => { + const data = await projectService.getGithubRepositories(url); + + return data; + }; + + const { + data: paginatedData, + size, + setSize, + isValidating, + } = useSWRInfinite(getKey, fetchGithubRepos); + + const userRepositories = (paginatedData ?? []).map((data) => data.repositories).flat(); + const totalCount = paginatedData && paginatedData.length > 0 ? paginatedData[0].total_count : 0; + + const options = + userRepositories.map((repo) => ({ + value: repo.id, + query: repo.full_name, + content:

{truncateText(repo.full_name, characterLimit)}

, + })) ?? []; + + return ( + { + const repo = userRepositories.find((repo) => repo.id === val); + + onChange(repo); + }} + label={label} + footerOption={ + <> + {userRepositories && options.length < totalCount && ( + + )} + + } + position="right" + optionsClassName="w-full" + /> + ); +}; diff --git a/apps/app/components/integration/github/single-user-select.tsx b/apps/app/components/integration/github/single-user-select.tsx new file mode 100644 index 000000000..e8204deb0 --- /dev/null +++ b/apps/app/components/integration/github/single-user-select.tsx @@ -0,0 +1,133 @@ +import Image from "next/image"; +import { useRouter } from "next/router"; + +import useSWR from "swr"; + +// services +import workspaceService from "services/workspace.service"; +// ui +import { Avatar, CustomSearchSelect, CustomSelect, Input } from "components/ui"; +// types +import { IGithubRepoCollaborator } from "types"; +import { IUserDetails } from "./root"; +// fetch-keys +import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; + +type Props = { + collaborator: IGithubRepoCollaborator; + index: number; + users: IUserDetails[]; + setUsers: React.Dispatch>; +}; + +const importOptions = [ + { + key: "map", + label: "Map to existing", + }, + { + key: "invite", + label: "Invite by email", + }, + { + key: false, + label: "Do not import", + }, +]; + +export const SingleUserSelect: React.FC = ({ collaborator, index, users, setUsers }) => { + const router = useRouter(); + const { workspaceSlug } = router.query; + + const { data: members } = useSWR( + workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug as string) : null, + workspaceSlug ? () => workspaceService.workspaceMembers(workspaceSlug as string) : null + ); + + const options = + members?.map((member) => ({ + value: member.member.email, + query: + (member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + : member.member.email) + + " " + + member.member.last_name ?? "", + content: ( +
+ + {member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + "(" + member.member.email + ")" + : member.member.email} +
+ ), + })) ?? []; + + return ( +
+
+
+ {`${collaborator.login} +
+

{collaborator.login}

+
+
+ + {importOptions.find((o) => o.key === users[index].import)?.label} +
+ } + onChange={(val: any) => { + const newUsers = [...users]; + newUsers[index].import = val; + newUsers[index].email = ""; + setUsers(newUsers); + }} + optionsClassName="w-full" + noChevron + > + {importOptions.map((option) => ( + +
{option.label}
+
+ ))} + +
+ {users[index].import === "invite" && ( + { + const newUsers = [...users]; + newUsers[index].email = e.target.value; + setUsers(newUsers); + }} + placeholder="Enter email of the user" + className="py-1 border-gray-200 text-xs" + /> + )} + {users[index].import === "map" && members && ( + { + const newUsers = [...users]; + newUsers[index].email = val; + setUsers(newUsers); + }} + optionsClassName="w-full" + /> + )} +
+ ); +}; diff --git a/apps/app/components/integration/github/users-select.tsx b/apps/app/components/integration/github/users-select.tsx deleted file mode 100644 index d67325d1a..000000000 --- a/apps/app/components/integration/github/users-select.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { FC } from "react"; -// types -import { IIntegrationData } from "components/integration"; - -type Props = { state: IIntegrationData; handleState: (key: string, valve: any) => void }; - -export const GithubUsersSelect: FC = ({ state, handleState }) => ( -
-
Users Select
-
- - -
-
-); diff --git a/apps/app/components/integration/guide.tsx b/apps/app/components/integration/guide.tsx index 366f681f1..347ce2628 100644 --- a/apps/app/components/integration/guide.tsx +++ b/apps/app/components/integration/guide.tsx @@ -1,162 +1,198 @@ -import { FC } from "react"; -// next imports +import { FC, useState } from "react"; + import Link from "next/link"; import Image from "next/image"; +import { useRouter } from "next/router"; + +import { mutate } from "swr"; + // icons import { ArrowRightIcon } from "components/icons"; -import GithubLogo from "public/logos/github-square.png"; -import { ChevronDownIcon } from "@heroicons/react/24/outline"; // components -import { Loader } from "components/ui"; import { GithubIntegrationRoot } from "components/integration"; +// icons +import { ArrowPathIcon } from "@heroicons/react/24/outline"; +// ui +import { Loader, PrimaryButton, SecondaryButton } from "components/ui"; +// images +import GithubLogo from "public/logos/github-square.png"; +// helpers +import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; // types -import { IAppIntegrations } from "types"; +import { IAppIntegrations, IImporterService, IWorkspaceIntegrations } from "types"; +// fetch-keys +import { IMPORTER_SERVICES_LIST } from "constants/fetch-keys"; type Props = { - workspaceSlug: string | undefined; provider: string | undefined; - allIntegrations: IAppIntegrations[] | undefined; - allIntegrationsError: Error | undefined; - allWorkspaceIntegrations: any | undefined; - allWorkspaceIntegrationsError: Error | undefined; - allIntegrationImporters: any | undefined; - allIntegrationImportersError: Error | undefined; + appIntegrations: IAppIntegrations[] | undefined; + workspaceIntegrations: IWorkspaceIntegrations[] | undefined; + importerServices: IImporterService[] | undefined; +}; + +const importersList: { [key: string]: string } = { + github: "GitHub", }; const IntegrationGuide: FC = ({ - workspaceSlug, provider, - allIntegrations, - allIntegrationsError, - allWorkspaceIntegrations, - allWorkspaceIntegrationsError, - allIntegrationImporters, - allIntegrationImportersError, -}) => ( -
- {!provider && ( - <> -
Import
+ appIntegrations, + workspaceIntegrations, + importerServices, +}) => { + const [refreshing, setRefreshing] = useState(false); -
-
-
Relocation Guide
-
- You can now transfer all the issues that you’ve created in other tracking services. - This tool will guide you to relocate the issue to Plane. + const router = useRouter(); + const { workspaceSlug } = router.query; + + return ( +
+ {!provider && ( + <> +
+
+
Relocation Guide
+
+ You can now transfer all the issues that you{"'"}ve created in other tracking + services. This tool will guide you to relocate the issue to Plane. +
+
+
+
Read More
+
+ +
-
-
Read More
-
- -
-
-
- -
- {allIntegrations && !allIntegrationsError ? ( - <> - {allIntegrations && allIntegrations.length > 0 ? ( - <> - {allIntegrations.map((_integration, _idx) => ( -
-
-
- {_integration?.provider === "github" && ( - GithubLogo - )} -
-
-
-
{_integration?.title}
-
- 0 -
-
-
- Activate GitHub integrations on individual projects to sync with - specific repositories. -
-
-
- - - -
-
- -
-
- -
- {allIntegrationImporters && !allIntegrationImportersError ? ( - <> - {allIntegrationImporters && allIntegrationImporters.length > 0 ? ( - <> - ) : ( -
- Previous Imports not available. -
- )} - - ) : ( -
- - {["", ""].map((_integration, _idx) => ( - - ))} - -
+
+ {appIntegrations ? ( + appIntegrations.length > 0 ? ( + appIntegrations.map((integration, index) => ( +
+
+
+ {integration?.provider === "github" && ( + GithubLogo )}
+
+
+

{integration?.title}

+
+
+ Activate GitHub integrations on individual projects to sync with specific + repositories. +
+
+
+ + Integrate Now + +
- ))} - + +

+ Previous Imports + +

+ {importerServices ? ( + importerServices.length > 0 ? ( +
+
+ {importerServices.map((service) => ( +
+

+ + Import from{" "} + + {importersList[service.service]} + {" "} + to{" "} + + {service.project_detail.name} + + + + {refreshing ? "Refreshing..." : service.status} + +

+
+ {renderShortDateWithYearFormat(service.created_at)}| + + Imported by{" "} + {service.initiated_by_detail.first_name && + service.initiated_by_detail.first_name !== "" + ? service.initiated_by_detail.first_name + + " " + + service.initiated_by_detail.last_name + : service.initiated_by_detail.email} + +
+
+ ))} +
+
+ ) : ( +
+ No previous imports available. +
+ ) + ) : ( + + + + + + + )} +
+ )) ) : (
Integrations not available.
- )} - - ) : ( -
+ ) + ) : ( - {["", ""].map((_integration, _idx) => ( - - ))} + + -
- )} -
- - )} + )} +
+ + )} - {provider && ( - <> - {provider === "github" && ( - - )} - - )} -
-); + {provider && provider === "github" && ( + + )} +
+ ); +}; export default IntegrationGuide; diff --git a/apps/app/components/integration/index.ts b/apps/app/components/integration/index.ts index 8bdacd967..bbe49c1ae 100644 --- a/apps/app/components/integration/index.ts +++ b/apps/app/components/integration/index.ts @@ -7,8 +7,10 @@ export * from "./github/auth"; // layout export * from "./github/root"; // components -export * from "./github/configure"; +export * from "./github/import-configure"; export * from "./github/import-data"; -export * from "./github/issues-select"; -export * from "./github/users-select"; -export * from "./github/confirm"; +export * from "./github/repo-details"; +export * from "./github/import-users"; +export * from "./github/import-confirm"; +export * from "./github/select-repository"; +export * from "./github/single-user-select"; diff --git a/apps/app/components/popup/index.tsx b/apps/app/components/popup/index.tsx index 0db8db0b7..5e3971a39 100644 --- a/apps/app/components/popup/index.tsx +++ b/apps/app/components/popup/index.tsx @@ -6,7 +6,7 @@ import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; // services -import workspaceService from "services/workspace.service"; +import IntegrationService from "services/integration"; // hooks import useToast from "hooks/use-toast"; // ui @@ -56,7 +56,9 @@ const OAuthPopUp = ({ integration }: any) => { const { data: workspaceIntegrations } = useSWR( workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null, () => - workspaceSlug ? workspaceService.getWorkspaceIntegrations(workspaceSlug as string) : null + workspaceSlug + ? IntegrationService.getWorkspaceIntegrationsList(workspaceSlug as string) + : null ); const handleRemoveIntegration = async () => { @@ -68,8 +70,10 @@ const OAuthPopUp = ({ integration }: any) => { setDeletingIntegration(true); - await workspaceService - .deleteWorkspaceIntegration(workspaceSlug as string, workspaceIntegrationId ?? "") + await IntegrationService.deleteWorkspaceIntegration( + workspaceSlug as string, + workspaceIntegrationId ?? "" + ) .then(() => { mutate( WORKSPACE_INTEGRATIONS(workspaceSlug as string), diff --git a/apps/app/components/project/single-integration-card.tsx b/apps/app/components/project/single-integration-card.tsx index dc2630a2e..4877f12a9 100644 --- a/apps/app/components/project/single-integration-card.tsx +++ b/apps/app/components/project/single-integration-card.tsx @@ -1,22 +1,18 @@ -import React, { useState } from "react"; +import React from "react"; import Image from "next/image"; import useSWR, { mutate } from "swr"; -import useSWRInfinite from "swr/infinite"; -// headless ui -import { Combobox, Transition } from "@headlessui/react"; // services import projectService from "services/project.service"; // hooks import { useRouter } from "next/router"; import useToast from "hooks/use-toast"; +// components +import { SelectRepository } from "components/integration"; // icons -import { CheckIcon, ChevronDownIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import GithubLogo from "public/logos/github-square.png"; -// helpers -import { truncateText } from "helpers/string.helper"; // types import { IWorkspaceIntegrations } from "types"; // fetch-keys @@ -27,8 +23,6 @@ type Props = { }; export const SingleIntegration: React.FC = ({ integration }) => { - const [query, setQuery] = useState(""); - const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -46,29 +40,6 @@ export const SingleIntegration: React.FC = ({ integration }) => { : null ); - const getKey = (pageIndex: number) => { - if (!workspaceSlug || !integration) return; - - return `${ - process.env.NEXT_PUBLIC_API_BASE_URL - }/api/workspaces/${workspaceSlug}/workspace-integrations/${ - integration.id - }/github-repositories/?page=${++pageIndex}`; - }; - - const fetchGithubRepos = async (url: string) => { - const data = await projectService.getGithubRepositories(url); - - return data; - }; - - const { - data: paginatedData, - size, - setSize, - isValidating, - } = useSWRInfinite(getKey, fetchGithubRepos); - const handleChange = (repo: any) => { if (!workspaceSlug || !projectId || !integration) return; @@ -107,21 +78,6 @@ export const SingleIntegration: React.FC = ({ integration }) => { }); }; - const userRepositories = (paginatedData ?? []).map((data) => data.repositories).flat(); - const totalCount = paginatedData && paginatedData.length > 0 ? paginatedData[0].total_count : 0; - - const options = - userRepositories.map((repo) => ({ - value: repo.id, - query: repo.full_name, - content:

{truncateText(repo.full_name, 25)}

, - })) ?? []; - - const filteredOptions = - query === "" - ? options - : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase())); - return ( <> {integration && ( @@ -137,95 +93,20 @@ export const SingleIntegration: React.FC = ({ integration }) => {

Select GitHub repository to enable sync.

- 0 ? `${syncedGithubRepository[0].repo_detail.owner}/${syncedGithubRepository[0].repo_detail.name}` : null } - onChange={(val: string) => { - const repo = userRepositories.find((repo) => repo.id === val); - - handleChange(repo); - }} - className="relative flex-shrink-0 text-left" - > - {({ open }: any) => ( - <> - - {syncedGithubRepository && syncedGithubRepository.length > 0 - ? `${syncedGithubRepository[0].repo_detail.owner}/${syncedGithubRepository[0].repo_detail.name}` - : "Select Repository"} - - - - -
- - setQuery(e.target.value)} - placeholder="Type to search..." - displayValue={(assigned: any) => assigned?.name} - /> -
-
-

- {options.length} of {totalCount} repositories -

- {paginatedData ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `${active || selected ? "bg-hover-gray" : ""} ${ - selected ? "font-medium" : "" - } flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5 text-gray-500` - } - > - {({ selected }) => ( - <> - {option.content} - {selected && } - - )} - - )) - ) : ( -

No matching results

- ) - ) : ( -

Loading...

- )} - {userRepositories && options.length < totalCount && ( - - )} -
-
-
- - )} -
+ label={ + syncedGithubRepository && syncedGithubRepository.length > 0 + ? `${syncedGithubRepository[0].repo_detail.owner}/${syncedGithubRepository[0].repo_detail.name}` + : "Select Repository" + } + onChange={handleChange} + />
)} diff --git a/apps/app/components/ui/custom-search-select.tsx b/apps/app/components/ui/custom-search-select.tsx index a085e2e3b..c8cea10be 100644 --- a/apps/app/components/ui/custom-search-select.tsx +++ b/apps/app/components/ui/custom-search-select.tsx @@ -134,15 +134,21 @@ export const CustomSearchSelect = ({ {({ active, selected }) => ( <> {option.content} -
+ {multiple ? ( +
+ +
+ ) : ( -
+ )} )} diff --git a/apps/app/constants/fetch-keys.ts b/apps/app/constants/fetch-keys.ts index 6081b4d56..f94ca2bff 100644 --- a/apps/app/constants/fetch-keys.ts +++ b/apps/app/constants/fetch-keys.ts @@ -23,12 +23,9 @@ const paramsToKey = (params: any) => { export const CURRENT_USER = "CURRENT_USER"; export const USER_WORKSPACE_INVITATIONS = "USER_WORKSPACE_INVITATIONS"; export const USER_WORKSPACES = "USER_WORKSPACES"; -export const APP_INTEGRATIONS = "APP_INTEGRATIONS"; export const WORKSPACE_DETAILS = (workspaceSlug: string) => `WORKSPACE_DETAILS_${workspaceSlug.toUpperCase()}`; -export const WORKSPACE_INTEGRATIONS = (workspaceSlug: string) => - `WORKSPACE_INTEGRATIONS_${workspaceSlug.toUpperCase()}`; export const WORKSPACE_MEMBERS = (workspaceSlug: string) => `WORKSPACE_MEMBERS_${workspaceSlug.toUpperCase()}`; @@ -120,6 +117,17 @@ export const ISSUE_DETAILS = (issueId: string) => `ISSUE_DETAILS_${issueId.toUpp export const SUB_ISSUES = (issueId: string) => `SUB_ISSUES_${issueId.toUpperCase()}`; // integrations +export const APP_INTEGRATIONS = "APP_INTEGRATIONS"; +export const WORKSPACE_INTEGRATIONS = (workspaceSlug: string) => + `WORKSPACE_INTEGRATIONS_${workspaceSlug.toUpperCase()}`; + +//import-export +export const IMPORTER_SERVICES_LIST = (workspaceSlug: string) => + `IMPORTER_SERVICES_LIST_${workspaceSlug.toUpperCase()}`; + +// github-importer +export const GITHUB_REPOSITORY_INFO = (workspaceSlug: string, repoName: string) => + `GITHUB_REPO_INFO_${workspaceSlug.toString().toUpperCase()}_${repoName.toUpperCase()}`; // Calendar export const PROJECT_CALENDAR_ISSUES = (projectId: string) => diff --git a/apps/app/layouts/settings-navbar.tsx b/apps/app/layouts/settings-navbar.tsx index b990eaf48..0aa413892 100644 --- a/apps/app/layouts/settings-navbar.tsx +++ b/apps/app/layouts/settings-navbar.tsx @@ -29,10 +29,10 @@ const SettingsNavbar: React.FC = ({ profilePage = false }) => { label: "Integrations", href: `/${workspaceSlug}/settings/integrations`, }, - // { - // label: "Import/Export", - // href: `/${workspaceSlug}/settings/import-export`, - // }, + { + label: "Import/Export", + href: `/${workspaceSlug}/settings/import-export`, + }, ]; const projectLinks: Array<{ diff --git a/apps/app/next.config.js b/apps/app/next.config.js index 2b3583fa4..d194a6cc7 100644 --- a/apps/app/next.config.js +++ b/apps/app/next.config.js @@ -10,6 +10,7 @@ const nextConfig = { "planefs-staging.s3.ap-south-1.amazonaws.com", "planefs.s3.amazonaws.com", "images.unsplash.com", + "avatars.githubusercontent.com", ], }, output: "standalone", diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx index 1db78de1e..e955dbc7b 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import { useRouter } from "next/router"; @@ -9,8 +9,10 @@ import { requiredAdmin } from "lib/auth"; // layouts import AppLayout from "layouts/app-layout"; // services -import workspaceService from "services/workspace.service"; +import IntegrationService from "services/integration"; import projectService from "services/project.service"; +// components +import { SingleIntegration } from "components/project"; // ui import { EmptySpace, EmptySpaceItem, Loader } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; @@ -21,7 +23,6 @@ import { IProject, UserAuth } from "types"; import type { NextPageContext, NextPage } from "next"; // fetch-keys import { PROJECT_DETAILS, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys"; -import { SingleIntegration } from "components/project"; const ProjectIntegrations: NextPage = (props) => { const { isMember, isOwner, isViewer, isGuest } = props; @@ -39,7 +40,9 @@ const ProjectIntegrations: NextPage = (props) => { const { data: workspaceIntegrations } = useSWR( workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null, () => - workspaceSlug ? workspaceService.getWorkspaceIntegrations(workspaceSlug as string) : null + workspaceSlug + ? IntegrationService.getWorkspaceIntegrationsList(workspaceSlug as string) + : null ); return ( diff --git a/apps/app/pages/[workspaceSlug]/settings/import-export.tsx b/apps/app/pages/[workspaceSlug]/settings/import-export.tsx index 32ddc3298..a51d08efc 100644 --- a/apps/app/pages/[workspaceSlug]/settings/import-export.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/import-export.tsx @@ -1,10 +1,11 @@ -// next imports -import type { GetServerSideProps, NextPage } from "next"; import { useRouter } from "next/router"; -// swr + import useSWR from "swr"; + // lib import { requiredWorkspaceAdmin } from "lib/auth"; +// services +import IntegrationService from "services/integration"; // hooks import useToast from "hooks/use-toast"; // layouts @@ -13,76 +14,55 @@ import IntegrationGuide from "components/integration/guide"; // ui import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // types -import { UserAuth, IAppIntegrations } from "types"; -// api services -import WorkspaceIntegrationService from "services/integration"; +import { UserAuth } from "types"; +import type { GetServerSideProps, NextPage } from "next"; +// fetch-keys +import { + APP_INTEGRATIONS, + IMPORTER_SERVICES_LIST, + WORKSPACE_INTEGRATIONS, +} from "constants/fetch-keys"; const ImportExport: NextPage = (props) => { const { setToastAlert } = useToast(); const router = useRouter(); - const { workspaceSlug, provider } = router.query as { - workspaceSlug: string; - provider: string; - }; + const { workspaceSlug, provider } = router.query; - // fetching all the integrations available - const { data: allIntegrations, error: allIntegrationsError } = useSWR< - IAppIntegrations[] | undefined, - Error - >( - workspaceSlug ? `ALL_INTEGRATIONS_${workspaceSlug.toUpperCase()}` : null, - workspaceSlug ? () => WorkspaceIntegrationService.listAllIntegrations() : null + const { data: appIntegrations } = useSWR(APP_INTEGRATIONS, () => + IntegrationService.getAppIntegrationsList() ); - // fetching all the integrations available - const { data: allWorkspaceIntegrations, error: allWorkspaceIntegrationsError } = useSWR< - any | undefined, - Error - >( - workspaceSlug ? `WORKSPACE_INTEGRATIONS_${workspaceSlug.toUpperCase()}` : null, + const { data: workspaceIntegrations } = useSWR( + workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null, workspaceSlug - ? () => WorkspaceIntegrationService.listWorkspaceIntegrations(workspaceSlug) + ? () => IntegrationService.getWorkspaceIntegrationsList(workspaceSlug as string) : null ); - // fetching list of importers that already initialized - const { data: allIntegrationImporters, error: allIntegrationImportersError } = useSWR< - any | undefined, - Error - >( - workspaceSlug ? `INTEGRATION_IMPORTERS_${workspaceSlug.toUpperCase()}` : null, - workspaceSlug - ? () => WorkspaceIntegrationService.fetchImportExportIntegrationStatus(workspaceSlug) - : null + const { data: importerServices } = useSWR( + workspaceSlug ? IMPORTER_SERVICES_LIST(workspaceSlug as string) : null, + workspaceSlug ? () => IntegrationService.getImporterServicesList(workspaceSlug as string) : null ); return ( - <> - - - - - } - settingsLayout - > -
- -
-
- + + + + + } + settingsLayout + > + + ); }; diff --git a/apps/app/pages/[workspaceSlug]/settings/integrations.tsx b/apps/app/pages/[workspaceSlug]/settings/integrations.tsx index f55eb22b3..e4dfd916f 100644 --- a/apps/app/pages/[workspaceSlug]/settings/integrations.tsx +++ b/apps/app/pages/[workspaceSlug]/settings/integrations.tsx @@ -6,6 +6,7 @@ import useSWR from "swr"; // services import workspaceService from "services/workspace.service"; +import IntegrationService from "services/integration"; // lib import { requiredWorkspaceAdmin } from "lib/auth"; // layouts @@ -30,8 +31,8 @@ const WorkspaceIntegrations: NextPage = (props) => { () => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null) ); - const { data: integrations } = useSWR(workspaceSlug ? APP_INTEGRATIONS : null, () => - workspaceSlug ? workspaceService.getIntegrations() : null + const { data: appIntegrations } = useSWR(workspaceSlug ? APP_INTEGRATIONS : null, () => + workspaceSlug ? IntegrationService.getAppIntegrationsList() : null ); return ( @@ -52,8 +53,8 @@ const WorkspaceIntegrations: NextPage = (props) => {

Integrations

- {integrations ? ( - integrations.map((integration) => ( + {appIntegrations ? ( + appIntegrations.map((integration) => ( { return this.get( `/api/workspaces/${workspaceSlug}/workspace-integrations/${integrationSlug}/github-repositories` @@ -20,21 +20,27 @@ class GithubIntegrationService extends APIService { }); } - // fetching repository stats under the repository eg: users, labels and issues - async fetchRepositoryStats(workspaceSlug: string): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/importers/${integrationServiceType}/`) + async getGithubRepoInfo( + workspaceSlug: string, + params: { owner: string; repo: string } + ): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/importers/${integrationServiceType}/`, { + params, + }) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - // migrating repository data in workspace project - async migrateRepositoryStatToProject( + async createGithubServiceImport( workspaceSlug: string, - integrationSlug: string + data: IGithubServiceImportFormData ): Promise { - return this.post(`/api/workspaces/${workspaceSlug}/importers/${integrationServiceType}/`) + return this.post( + `/api/workspaces/${workspaceSlug}/projects/importers/${integrationServiceType}/`, + data + ) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; diff --git a/apps/app/services/integration/index.ts b/apps/app/services/integration/index.ts index af85241ae..454cce996 100644 --- a/apps/app/services/integration/index.ts +++ b/apps/app/services/integration/index.ts @@ -1,16 +1,15 @@ import APIService from "services/api.service"; // types -import { IAppIntegrations, IWorkspaceIntegrations, IProject } from "types"; +import { IAppIntegrations, IImporterService, IWorkspaceIntegrations } from "types"; const { NEXT_PUBLIC_API_BASE_URL } = process.env; -class WorkspaceIntegrationService extends APIService { +class IntegrationService extends APIService { constructor() { super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); } - // integration available and integration validation starts - async listAllIntegrations(): Promise { + async getAppIntegrationsList(): Promise { return this.get(`/api/integrations/`) .then((response) => response?.data) .catch((error) => { @@ -18,26 +17,25 @@ class WorkspaceIntegrationService extends APIService { }); } - async listWorkspaceIntegrations(workspaceSlug: string): Promise { + async getWorkspaceIntegrationsList(workspaceSlug: string): Promise { return this.get(`/api/workspaces/${workspaceSlug}/workspace-integrations/`) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - // integration available and integration validation ends - // listing all the projects under the workspace - async listWorkspaceProjects(workspaceSlug: string): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/projects/`) - .then((response) => response?.data) + async deleteWorkspaceIntegration(workspaceSlug: string, integrationId: string): Promise { + return this.delete( + `/api/workspaces/${workspaceSlug}/workspace-integrations/${integrationId}/provider/` + ) + .then((res) => res?.data) .catch((error) => { throw error?.response?.data; }); } - // fetching the status of all the importers that initiated eg: GitHub... - async fetchImportExportIntegrationStatus(workspaceSlug: string): Promise { + async getImporterServicesList(workspaceSlug: string): Promise { return this.get(`/api/workspaces/${workspaceSlug}/importers/`) .then((response) => response?.data) .catch((error) => { @@ -46,4 +44,4 @@ class WorkspaceIntegrationService extends APIService { } } -export default new WorkspaceIntegrationService(); +export default new IntegrationService(); diff --git a/apps/app/services/workspace.service.ts b/apps/app/services/workspace.service.ts index 1cb13d2b5..8010b66c7 100644 --- a/apps/app/services/workspace.service.ts +++ b/apps/app/services/workspace.service.ts @@ -10,8 +10,6 @@ import { IWorkspaceMember, IWorkspaceMemberInvitation, ILastActiveWorkspaceDetails, - IAppIntegrations, - IWorkspaceIntegrations, IWorkspaceSearchResults, } from "types"; @@ -195,32 +193,6 @@ class WorkspaceService extends APIService { }); } - async getIntegrations(): Promise { - return this.get(`/api/integrations/`) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - } - - async getWorkspaceIntegrations(workspaceSlug: string): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/workspace-integrations/`) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - } - - async deleteWorkspaceIntegration(workspaceSlug: string, integrationId: string): Promise { - return this.delete( - `/api/workspaces/${workspaceSlug}/workspace-integrations/${integrationId}/provider/` - ) - .then((res) => res?.data) - .catch((error) => { - throw error?.response?.data; - }); - } - async searchWorkspace( workspaceSlug: string, projectId: string, diff --git a/apps/app/types/importer/github-importer.d.ts b/apps/app/types/importer/github-importer.d.ts new file mode 100644 index 000000000..36220b626 --- /dev/null +++ b/apps/app/types/importer/github-importer.d.ts @@ -0,0 +1,33 @@ +export interface IGithubServiceImportFormData { + metadata: { + owner: string; + name: string; + repository_id: number; + url: string; + }; + data: { + users: { + username: string; + import: boolean | "invite" | "map"; + email: string; + }[]; + }; + config: { + sync: boolean; + }; + project_id: string; +} + +export interface IGithubRepoCollaborator { + avatar_url: string; + html_url: string; + id: number; + login: string; + url: string; +} + +export interface IGithubRepoInfo { + issue_count: number; + labels: number; + collaborators: IGithubRepoCollaborator[]; +} diff --git a/apps/app/types/importer/index.ts b/apps/app/types/importer/index.ts new file mode 100644 index 000000000..1e3f8c2ad --- /dev/null +++ b/apps/app/types/importer/index.ts @@ -0,0 +1,33 @@ +export * from "./github-importer"; + +import { IProjectLite } from "types/projects"; +// types +import { IUserLite } from "types/users"; + +export interface IImporterService { + created_at: string; + config: { + sync: boolean; + }; + created_by: string | null; + data: { + users: []; + }; + id: string; + initiated_by: string; + initiated_by_detail: IUserLite; + metadata: { + name: string; + owner: string; + repository_id: number; + url: string; + }; + project: string; + project_detail: IProjectLite; + service: string; + status: "processing" | "completed" | "failed"; + updated_at: string; + updated_by: string; + token: string; + workspace: string; +} diff --git a/apps/app/types/index.d.ts b/apps/app/types/index.d.ts index 9ba150dc1..bc8134e54 100644 --- a/apps/app/types/index.d.ts +++ b/apps/app/types/index.d.ts @@ -10,6 +10,7 @@ export * from "./views"; export * from "./integration"; export * from "./pages"; export * from "./ai"; +export * from "./importer"; export type NestedKeyOf = { [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object