From c503e8ebc9fe451d35af0536c9ae1cffa05c64aa Mon Sep 17 00:00:00 2001 From: Yi Date: Fri, 1 Nov 2024 14:55:56 +0800 Subject: [PATCH] chore: install package from GitHub --- .../workplace-selector/index.tsx | 8 -- .../plugins/install-plugin/hooks.ts | 68 +++++++++++ .../install-from-github/index.tsx | 113 ++++++++---------- .../install-from-github/steps/loaded.tsx | 55 +++++++++ .../{setVersion.tsx => selectPackage.tsx} | 39 +++++- .../install-from-github/steps/setPackage.tsx | 53 -------- .../install-from-github/steps/setURL.tsx | 2 + .../plugins/install-plugin/utils.ts | 8 ++ web/app/components/plugins/types.ts | 9 +- web/service/plugins.ts | 23 +++- 10 files changed, 239 insertions(+), 139 deletions(-) create mode 100644 web/app/components/plugins/install-plugin/hooks.ts create mode 100644 web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx rename web/app/components/plugins/install-plugin/install-from-github/steps/{setVersion.tsx => selectPackage.tsx} (57%) delete mode 100644 web/app/components/plugins/install-plugin/install-from-github/steps/setPackage.tsx diff --git a/web/app/components/header/account-dropdown/workplace-selector/index.tsx b/web/app/components/header/account-dropdown/workplace-selector/index.tsx index 4d21ccdb59..fd3a997436 100644 --- a/web/app/components/header/account-dropdown/workplace-selector/index.tsx +++ b/web/app/components/header/account-dropdown/workplace-selector/index.tsx @@ -6,15 +6,12 @@ import { RiArrowDownSLine } from '@remixicon/react' import cn from '@/utils/classnames' import { switchWorkspace } from '@/service/common' import { useWorkspacesContext } from '@/context/workspace-context' -import HeaderBillingBtn from '@/app/components/billing/header-billing-btn' -import { useProviderContext } from '@/context/provider-context' import { ToastContext } from '@/app/components/base/toast' const WorkplaceSelector = () => { const { t } = useTranslation() const { notify } = useContext(ToastContext) const { workspaces } = useWorkspacesContext() - const { enableBilling } = useProviderContext() const currentWorkspace = workspaces.find(v => v.current) const handleSwitchWorkspace = async (tenant_id: string) => { try { @@ -69,11 +66,6 @@ const WorkplaceSelector = () => {
handleSwitchWorkspace(workspace.id)}>
{workspace.name[0].toLocaleUpperCase()}
{workspace.name}
- {enableBilling && ( -
- -
- )}
)) } diff --git a/web/app/components/plugins/install-plugin/hooks.ts b/web/app/components/plugins/install-plugin/hooks.ts new file mode 100644 index 0000000000..2d4271e887 --- /dev/null +++ b/web/app/components/plugins/install-plugin/hooks.ts @@ -0,0 +1,68 @@ +import { useState } from 'react' +import Toast from '@/app/components/base/toast' +import { uploadGitHub } from '@/service/plugins' + +export const useGitHubReleases = () => { + const fetchReleases = async (owner: string, repo: string, setReleases: (releases: any) => void) => { + try { + const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases`) + if (!res.ok) throw new Error('Failed to fetch releases') + const data = await res.json() + + const formattedReleases = data.map((release: any) => ({ + tag_name: release.tag_name, + assets: release.assets.map((asset: any) => ({ + browser_download_url: asset.browser_download_url, + name: asset.name, + })), + })) + + setReleases(formattedReleases) + } + catch (error) { + Toast.notify({ + type: 'error', + message: 'Failed to fetch repository releases', + }) + } + } + + return { fetchReleases } +} + +export const useGitHubUpload = () => { + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + const handleUpload = async ( + repoUrl: string, + selectedVersion: string, + selectedPackage: string, + onSuccess?: (GitHubPackage: { manifest: any; uniqueIdentifier: string }) => void, + ) => { + setIsLoading(true) + setError(null) + + try { + const response = await uploadGitHub(repoUrl, selectedVersion, selectedPackage) + const GitHubPackage = { + manifest: response.manifest, + uniqueIdentifier: response.plugin_unique_identifier, + } + if (onSuccess) onSuccess(GitHubPackage) + return GitHubPackage + } + catch (error) { + setError('Error installing package') + Toast.notify({ + type: 'error', + message: 'Error installing package', + }) + } + finally { + setIsLoading(false) + } + } + + return { handleUpload, isLoading, error } +} diff --git a/web/app/components/plugins/install-plugin/install-from-github/index.tsx b/web/app/components/plugins/install-plugin/install-from-github/index.tsx index 06c4c0797a..890424bfed 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/index.tsx @@ -3,13 +3,16 @@ import React, { useState } from 'react' import Modal from '@/app/components/base/modal' import type { Item } from '@/app/components/base/select' -import type { GitHubUrlInfo, InstallState } from '@/app/components/plugins/types' +import type { InstallState } from '@/app/components/plugins/types' +import { useGitHubReleases, useGitHubUpload } from '../hooks' +import { parseGitHubUrl } from '../utils' +import type { PluginDeclaration } from '../../types' import { InstallStepFromGitHub } from '../../types' import Toast from '@/app/components/base/toast' import SetURL from './steps/setURL' -import SetVersion from './steps/setVersion' -import SetPackage from './steps/setPackage' +import SelectPackage from './steps/selectPackage' import Installed from './steps/installed' +import Loaded from './steps/loaded' import { useTranslation } from 'react-i18next' type InstallFromGitHubProps = { @@ -25,6 +28,8 @@ const InstallFromGitHub: React.FC = ({ onClose }) => { selectedPackage: '', releases: [], }) + const [uniqueIdentifier, setUniqueIdentifier] = useState(null) + const [manifest, setManifest] = useState(null) const versions: Item[] = state.releases.map(release => ({ value: release.tag_name, @@ -36,41 +41,16 @@ const InstallFromGitHub: React.FC = ({ onClose }) => { .find(release => release.tag_name === state.selectedVersion) ?.assets .map(asset => ({ - value: asset.browser_download_url, + value: asset.name, name: asset.name, })) || []) : [] - const parseGitHubUrl = (url: string): GitHubUrlInfo => { - const githubUrlRegex = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/?$/ - const match = url.match(githubUrlRegex) - - if (match) { - return { - isValid: true, - owner: match[1], - repo: match[2], - } - } - - return { isValid: false } - } + const { isLoading, handleUpload, error } = useGitHubUpload() + const { fetchReleases } = useGitHubReleases() const handleInstall = async () => { - // try { - // const response = await installPackageFromGitHub({ repo: state.repoUrl, version: state.selectedVersion, package: state.selectedPackage }) - // if (response.plugin_unique_identifier) { - // setState(prevState => ({...prevState, step: InstallStep.installed})) - // console.log('Package installed:') - // } - // else { - // console.error('Failed to install package:') - // } - // } - // catch (error) { - // console.error('Error installing package:') - // } - setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed })) + } const handleNext = async () => { @@ -84,45 +64,48 @@ const InstallFromGitHub: React.FC = ({ onClose }) => { }) break } - try { - const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases`) - if (!res.ok) - throw new Error('Failed to fetch releases') - const data = await res.json() - const formattedReleases = data.map((release: any) => ({ - tag_name: release.tag_name, - assets: release.assets.map((asset: any) => ({ - browser_download_url: asset.browser_download_url, - id: asset.id, - name: asset.name, - })), + await fetchReleases(owner, repo, (fetchedReleases) => { + setState(prevState => ({ + ...prevState, + releases: fetchedReleases, + step: InstallStepFromGitHub.selectPackage, })) - setState(prevState => ({ ...prevState, releases: formattedReleases, step: InstallStepFromGitHub.setVersion })) - } - catch (error) { + }) + break + } + case InstallStepFromGitHub.selectPackage: { + const repo = state.repoUrl.replace('https://github.com/', '') + if (error) { Toast.notify({ type: 'error', - message: 'Failed to fetch repository release', + message: error, + }) + } + else { + await handleUpload(repo, state.selectedVersion, state.selectedPackage, (GitHubPackage) => { + setManifest(GitHubPackage.manifest) + setUniqueIdentifier(GitHubPackage.uniqueIdentifier) + setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.loaded })) }) } break } - case InstallStepFromGitHub.setVersion: - setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.setPackage })) - break - case InstallStepFromGitHub.setPackage: + case InstallStepFromGitHub.loaded: + setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed })) handleInstall() break + case InstallStepFromGitHub.installed: + break } } const handleBack = () => { setState((prevState) => { switch (prevState.step) { - case InstallStepFromGitHub.setVersion: + case InstallStepFromGitHub.selectPackage: return { ...prevState, step: InstallStepFromGitHub.setUrl } - case InstallStepFromGitHub.setPackage: - return { ...prevState, step: InstallStepFromGitHub.setVersion } + case InstallStepFromGitHub.loaded: + return { ...prevState, step: InstallStepFromGitHub.selectPackage } default: return prevState } @@ -155,22 +138,24 @@ const InstallFromGitHub: React.FC = ({ onClose }) => { onCancel={onClose} /> )} - {state.step === InstallStepFromGitHub.setVersion && ( - setState(prevState => ({ ...prevState, selectedVersion: item.value as string }))} + onSelectVersion={item => setState(prevState => ({ ...prevState, selectedVersion: item.value as string }))} + selectedPackage={state.selectedPackage} + packages={packages} + onSelectPackage={item => setState(prevState => ({ ...prevState, selectedPackage: item.value as string }))} onNext={handleNext} onBack={handleBack} /> )} - {state.step === InstallStepFromGitHub.setPackage && ( - setState(prevState => ({ ...prevState, selectedPackage: item.value as string }))} - onInstall={handleInstall} + {state.step === InstallStepFromGitHub.loaded && ( + )} {state.step === InstallStepFromGitHub.installed && ( diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx new file mode 100644 index 0000000000..42d847c584 --- /dev/null +++ b/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx @@ -0,0 +1,55 @@ +'use client' + +import React from 'react' +import Button from '@/app/components/base/button' +import type { PluginDeclaration } from '../../../types' +import Card from '../../../card' +import Badge, { BadgeState } from '@/app/components/base/badge/index' +import { pluginManifestToCardPluginProps } from '../../utils' +import { useTranslation } from 'react-i18next' + +type LoadedProps = { + isLoading: boolean + payload: PluginDeclaration + onBack: () => void + onInstall: () => void +} + +const i18nPrefix = 'plugin.installModal' + +const Loaded: React.FC = ({ isLoading, payload, onBack, onInstall }) => { + const { t } = useTranslation() + return ( + <> +
+

{t(`${i18nPrefix}.readyToInstall`)}

+
+
+ {payload.version}} + /> +
+
+ + +
+ + ) +} + +export default Loaded diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/setVersion.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx similarity index 57% rename from web/app/components/plugins/install-plugin/install-from-github/steps/setVersion.tsx rename to web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx index 042ab2b093..70ef1d8522 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/steps/setVersion.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx @@ -1,18 +1,32 @@ +'use client' + import React from 'react' import type { Item } from '@/app/components/base/select' import { PortalSelect } from '@/app/components/base/select' import Button from '@/app/components/base/button' import { useTranslation } from 'react-i18next' -type SetVersionProps = { +type SelectPackageProps = { selectedVersion: string versions: Item[] - onSelect: (item: Item) => void + onSelectVersion: (item: Item) => void + selectedPackage: string + packages: Item[] + onSelectPackage: (item: Item) => void onNext: () => void onBack: () => void } -const SetVersion: React.FC = ({ selectedVersion, versions, onSelect, onNext, onBack }) => { +const SelectPackage: React.FC = ({ + selectedVersion, + versions, + onSelectVersion, + selectedPackage, + packages, + onSelectPackage, + onNext, + onBack, +}) => { const { t } = useTranslation() return ( <> @@ -24,11 +38,24 @@ const SetVersion: React.FC = ({ selectedVersion, versions, onSe + +
@@ -50,4 +77,4 @@ const SetVersion: React.FC = ({ selectedVersion, versions, onSe ) } -export default SetVersion +export default SelectPackage diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/setPackage.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/setPackage.tsx deleted file mode 100644 index 2db55aa56f..0000000000 --- a/web/app/components/plugins/install-plugin/install-from-github/steps/setPackage.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react' -import type { Item } from '@/app/components/base/select' -import { PortalSelect } from '@/app/components/base/select' -import Button from '@/app/components/base/button' -import { useTranslation } from 'react-i18next' - -type SetPackageProps = { - selectedPackage: string - packages: Item[] - onSelect: (item: Item) => void - onInstall: () => void - onBack: () => void -} - -const SetPackage: React.FC = ({ selectedPackage, packages, onSelect, onInstall, onBack }) => { - const { t } = useTranslation() - return ( - <> - - -
- - -
- - ) -} - -export default SetPackage diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/setURL.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/setURL.tsx index 9ec6cd6eee..c6ce006f37 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/steps/setURL.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/steps/setURL.tsx @@ -1,3 +1,5 @@ +'use client' + import React from 'react' import Button from '@/app/components/base/button' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/plugins/install-plugin/utils.ts b/web/app/components/plugins/install-plugin/utils.ts index 8b3e850deb..7677bf5b73 100644 --- a/web/app/components/plugins/install-plugin/utils.ts +++ b/web/app/components/plugins/install-plugin/utils.ts @@ -1,4 +1,5 @@ import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../types' +import type { GitHubUrlInfo } from '@/app/components/plugins/types' export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaration): Plugin => { return { @@ -18,6 +19,7 @@ export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaratio endpoint: { settings: [], }, + tags: [], } } @@ -39,5 +41,11 @@ export const pluginManifestInMarketToPluginProps = (pluginManifest: PluginManife endpoint: { settings: [], }, + tags: [], } } + +export const parseGitHubUrl = (url: string): GitHubUrlInfo => { + const match = url.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/?$/) + return match ? { isValid: true, owner: match[1], repo: match[2] } : { isValid: false } +} diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index ae16b0b300..f3cfd7f93c 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -133,8 +133,8 @@ export type Permissions = { export enum InstallStepFromGitHub { setUrl = 'url', - setVersion = 'version', - setPackage = 'package', + selectPackage = 'selecting', + loaded = 'loaded', installed = 'installed', } @@ -205,6 +205,11 @@ export type InstallPackageResponse = { task_id: string } +export type uploadGitHubResponse = { + plugin_unique_identifier: string + manifest: PluginDeclaration +} + export type DebugInfo = { key: string host: string diff --git a/web/service/plugins.ts b/web/service/plugins.ts index 43a57a24b4..e122b8efea 100644 --- a/web/service/plugins.ts +++ b/web/service/plugins.ts @@ -14,6 +14,7 @@ import type { TaskStatusResponse, UninstallPluginResponse, UpdateEndpointRequest, + uploadGitHubResponse, } from '@/app/components/plugins/types' import type { DebugInfo as DebugInfoTypes } from '@/app/components/plugins/types' import type { @@ -51,12 +52,6 @@ export const disableEndpoint: Fetcher(url, { body: { endpoint_id: endpointID } }) } -export const installPackageFromGitHub: Fetcher = ({ repo, version, package: packageName }) => { - return post('/workspaces/current/plugin/upload/github', { - body: { repo, version, package: packageName }, - }) -} - export const fetchDebugKey = async () => { return get('/workspaces/current/plugin/debugging-key') } @@ -76,6 +71,22 @@ export const installPackageFromLocal = async (uniqueIdentifier: string) => { }) } +export const uploadGitHub = async (repoUrl: string, selectedVersion: string, selectedPackage: string) => { + return post('/workspaces/current/plugin/upload/github', { + body: { + repo: repoUrl, + version: selectedVersion, + package: selectedPackage, + }, + }) +} + +export const installPackageFromGitHub = async (uniqueIdentifier: string) => { + return post('/workspaces/current/plugin/install/github', { + body: { plugin_unique_identifiers: [uniqueIdentifier] }, + }) +} + export const fetchIcon = (tenantId: string, fileName: string) => { return get(`workspaces/current/plugin/icon?tenant_id=${tenantId}&filename=${fileName}`) }