mirror of
https://github.com/langgenius/dify.git
synced 2025-12-19 17:27:16 -05:00
refactor: update install status handling in plugin installation process (#27594)
This commit is contained in:
@@ -214,6 +214,7 @@ const SettingBuiltInTool: FC<Props> = ({
|
||||
pluginPayload={{
|
||||
provider: collection.name,
|
||||
category: AuthCategory.tool,
|
||||
providerType: collection.type,
|
||||
}}
|
||||
credentialId={credentialId}
|
||||
onAuthorizationItemClick={onAuthorizationItemClick}
|
||||
|
||||
@@ -23,6 +23,10 @@ const meta = {
|
||||
args: {
|
||||
items: ITEMS,
|
||||
value: 'all',
|
||||
// eslint-disable-next-line no-empty-function
|
||||
onSelect: () => {},
|
||||
// eslint-disable-next-line no-empty-function
|
||||
onClear: () => {},
|
||||
},
|
||||
} satisfies Meta<typeof Chip>
|
||||
|
||||
@@ -69,6 +73,13 @@ const [selection, setSelection] = useState('all')
|
||||
}
|
||||
|
||||
export const WithoutLeftIcon: Story = {
|
||||
args: {
|
||||
showLeftIcon: false,
|
||||
// eslint-disable-next-line no-empty-function
|
||||
onSelect: () => {},
|
||||
// eslint-disable-next-line no-empty-function
|
||||
onClear: () => {},
|
||||
},
|
||||
render: args => (
|
||||
<ChipDemo
|
||||
{...args}
|
||||
|
||||
@@ -103,7 +103,6 @@ export const Default: Story = {
|
||||
</button>
|
||||
</>
|
||||
),
|
||||
children: null,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -112,7 +111,6 @@ export const WithoutFooter: Story = {
|
||||
args: {
|
||||
footer: undefined,
|
||||
title: 'Read-only summary',
|
||||
children: null,
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
@@ -130,7 +128,6 @@ export const CustomStyling: Story = {
|
||||
bodyClassName: 'bg-gray-50 rounded-xl p-5',
|
||||
footerClassName: 'justify-between px-4 pb-4 pt-4',
|
||||
titleClassName: 'text-lg text-primary-600',
|
||||
children: null,
|
||||
footer: (
|
||||
<>
|
||||
<span className="text-xs text-gray-400">Last synced 2 minutes ago</span>
|
||||
@@ -144,7 +141,6 @@ export const CustomStyling: Story = {
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
children: null,
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useDataSourceAuthUpdate } from './hooks'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { useGetDataSourceOAuthUrl } from '@/service/use-datasource'
|
||||
import { openOAuthPopup } from '@/hooks/use-oauth'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
|
||||
type CardProps = {
|
||||
item: DataSourceAuth
|
||||
@@ -42,6 +43,7 @@ const Card = ({
|
||||
const pluginPayload = {
|
||||
category: AuthCategory.datasource,
|
||||
provider: `${item.plugin_id}/${item.name}`,
|
||||
providerType: CollectionType.datasource,
|
||||
}
|
||||
const { handleAuthUpdate } = useDataSourceAuthUpdate({
|
||||
pluginId: item.plugin_id,
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react'
|
||||
import { InstallStep } from '../../types'
|
||||
import Install from './steps/install'
|
||||
import Installed from './steps/installed'
|
||||
import type { Dependency, InstallStatusResponse, Plugin } from '../../types'
|
||||
import type { Dependency, InstallStatus, Plugin } from '../../types'
|
||||
|
||||
type Props = {
|
||||
step: InstallStep
|
||||
@@ -26,8 +26,8 @@ const ReadyToInstall: FC<Props> = ({
|
||||
isFromMarketPlace,
|
||||
}) => {
|
||||
const [installedPlugins, setInstalledPlugins] = useState<Plugin[]>([])
|
||||
const [installStatus, setInstallStatus] = useState<InstallStatusResponse[]>([])
|
||||
const handleInstalled = useCallback((plugins: Plugin[], installStatus: InstallStatusResponse[]) => {
|
||||
const [installStatus, setInstallStatus] = useState<InstallStatus[]>([])
|
||||
const handleInstalled = useCallback((plugins: Plugin[], installStatus: InstallStatus[]) => {
|
||||
setInstallStatus(installStatus)
|
||||
setInstalledPlugins(plugins)
|
||||
onStepChange(InstallStep.installed)
|
||||
|
||||
@@ -2,23 +2,31 @@
|
||||
import type { FC } from 'react'
|
||||
import { useRef } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import type { Dependency, InstallStatusResponse, Plugin, VersionInfo } from '../../../types'
|
||||
import {
|
||||
type Dependency,
|
||||
type InstallStatus,
|
||||
type InstallStatusResponse,
|
||||
type Plugin,
|
||||
TaskStatus,
|
||||
type VersionInfo,
|
||||
} from '../../../types'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ExposeRefs } from './install-multi'
|
||||
import InstallMulti from './install-multi'
|
||||
import { useInstallOrUpdate } from '@/service/use-plugins'
|
||||
import { useInstallOrUpdate, usePluginTaskList } from '@/service/use-plugins'
|
||||
import useRefreshPluginList from '../../hooks/use-refresh-plugin-list'
|
||||
import { useCanInstallPluginFromMarketplace } from '@/app/components/plugins/plugin-page/use-reference-setting'
|
||||
import { useMittContextSelector } from '@/context/mitt-context'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import checkTaskStatus from '../../base/check-task-status'
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
type Props = {
|
||||
allPlugins: Dependency[]
|
||||
onStartToInstall?: () => void
|
||||
onInstalled: (plugins: Plugin[], installStatus: InstallStatusResponse[]) => void
|
||||
onInstalled: (plugins: Plugin[], installStatus: InstallStatus[]) => void
|
||||
onCancel: () => void
|
||||
isFromMarketPlace?: boolean
|
||||
isHideButton?: boolean
|
||||
@@ -55,22 +63,64 @@ const Install: FC<Props> = ({
|
||||
setCanInstall(true)
|
||||
}, [])
|
||||
|
||||
const {
|
||||
check,
|
||||
stop,
|
||||
} = checkTaskStatus()
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
stop()
|
||||
onCancel()
|
||||
}, [onCancel, stop])
|
||||
|
||||
const { handleRefetch } = usePluginTaskList()
|
||||
|
||||
// Install from marketplace and github
|
||||
const { mutate: installOrUpdate, isPending: isInstalling } = useInstallOrUpdate({
|
||||
onSuccess: (res: InstallStatusResponse[]) => {
|
||||
onSuccess: async (res: InstallStatusResponse[]) => {
|
||||
const isAllSettled = res.every(r => r.status === TaskStatus.success || r.status === TaskStatus.failed)
|
||||
// if all settled, return the install status
|
||||
if (isAllSettled) {
|
||||
onInstalled(selectedPlugins, res.map((r, i) => {
|
||||
return ({
|
||||
...r,
|
||||
success: r.status === TaskStatus.success,
|
||||
isFromMarketPlace: allPlugins[selectedIndexes[i]].type === 'marketplace',
|
||||
})
|
||||
}))
|
||||
const hasInstallSuccess = res.some(r => r.success)
|
||||
const hasInstallSuccess = res.some(r => r.status === TaskStatus.success)
|
||||
if (hasInstallSuccess) {
|
||||
refreshPluginList(undefined, true)
|
||||
emit('plugin:install:success', selectedPlugins.map((p) => {
|
||||
return `${p.plugin_id}/${p.name}`
|
||||
}))
|
||||
}
|
||||
return
|
||||
}
|
||||
// if not all settled, keep checking the status of the plugins
|
||||
handleRefetch()
|
||||
const installStatus = await Promise.all(res.map(async (item, index) => {
|
||||
if (item.status !== TaskStatus.running) {
|
||||
return {
|
||||
success: item.status === TaskStatus.success,
|
||||
isFromMarketPlace: allPlugins[selectedIndexes[index]].type === 'marketplace',
|
||||
}
|
||||
}
|
||||
const { status } = await check({
|
||||
taskId: item.taskId,
|
||||
pluginUniqueIdentifier: item.uniqueIdentifier,
|
||||
})
|
||||
return {
|
||||
success: status === TaskStatus.success,
|
||||
isFromMarketPlace: allPlugins[selectedIndexes[index]].type === 'marketplace',
|
||||
}
|
||||
}))
|
||||
onInstalled(selectedPlugins, installStatus)
|
||||
const hasInstallSuccess = installStatus.some(r => r.success)
|
||||
if (hasInstallSuccess) {
|
||||
emit('plugin:install:success', selectedPlugins.map((p) => {
|
||||
return `${p.plugin_id}/${p.name}`
|
||||
}))
|
||||
}
|
||||
},
|
||||
})
|
||||
const handleInstall = () => {
|
||||
@@ -150,7 +200,7 @@ const Install: FC<Props> = ({
|
||||
</div>
|
||||
<div className='flex items-center justify-end gap-2 self-stretch'>
|
||||
{!canInstall && (
|
||||
<Button variant='secondary' className='min-w-[72px]' onClick={onCancel}>
|
||||
<Button variant='secondary' className='min-w-[72px]' onClick={handleCancel}>
|
||||
{t('common.operation.cancel')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import type { InstallStatusResponse, Plugin } from '../../../types'
|
||||
import type { InstallStatus, Plugin } from '../../../types'
|
||||
import Card from '@/app/components/plugins/card'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -11,7 +11,7 @@ import { MARKETPLACE_API_PREFIX } from '@/config'
|
||||
|
||||
type Props = {
|
||||
list: Plugin[]
|
||||
installStatus: InstallStatusResponse[]
|
||||
installStatus: InstallStatus[]
|
||||
onCancel: () => void
|
||||
isHideButton?: boolean
|
||||
}
|
||||
|
||||
@@ -331,6 +331,7 @@ const DetailHeader = ({
|
||||
pluginPayload={{
|
||||
provider: provider?.name || '',
|
||||
category: AuthCategory.tool,
|
||||
providerType: provider?.type || '',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -314,6 +314,7 @@ const ToolSelector: FC<Props> = ({
|
||||
pluginPayload={{
|
||||
provider: currentProvider.name,
|
||||
category: AuthCategory.tool,
|
||||
providerType: currentProvider.type,
|
||||
}}
|
||||
credentialId={value?.credential_id}
|
||||
onAuthorizationItemClick={handleAuthorizationItemClick}
|
||||
|
||||
@@ -280,6 +280,12 @@ export type InstallPackageResponse = {
|
||||
}
|
||||
|
||||
export type InstallStatusResponse = {
|
||||
status: TaskStatus,
|
||||
taskId: string,
|
||||
uniqueIdentifier: string,
|
||||
}
|
||||
|
||||
export type InstallStatus = {
|
||||
success: boolean,
|
||||
isFromMarketPlace?: boolean
|
||||
}
|
||||
|
||||
1
web/global.d.ts
vendored
1
web/global.d.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import './types/i18n'
|
||||
import './types/jsx'
|
||||
import './types/mdx'
|
||||
import './types/assets'
|
||||
|
||||
declare module 'lamejs';
|
||||
declare module 'lamejs/src/js/MPEGMode';
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
Dependency,
|
||||
GitHubItemAndMarketPlaceDependency,
|
||||
InstallPackageResponse,
|
||||
InstallStatusResponse,
|
||||
InstalledLatestVersionResponse,
|
||||
InstalledPluginListWithTotalResponse,
|
||||
PackageDependency,
|
||||
@@ -233,7 +234,7 @@ export const useUploadGitHub = (payload: {
|
||||
export const useInstallOrUpdate = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
onSuccess?: (res: { success: boolean }[]) => void
|
||||
onSuccess?: (res: InstallStatusResponse[]) => void
|
||||
}) => {
|
||||
const { mutateAsync: updatePackageFromMarketPlace } = useUpdatePackageFromMarketPlace()
|
||||
|
||||
@@ -251,6 +252,8 @@ export const useInstallOrUpdate = ({
|
||||
const installedPayload = installedInfo[orgAndName]
|
||||
const isInstalled = !!installedPayload
|
||||
let uniqueIdentifier = ''
|
||||
let taskId = ''
|
||||
let isFinishedInstallation = false
|
||||
|
||||
if (item.type === 'github') {
|
||||
const data = item as GitHubItemAndMarketPlaceDependency
|
||||
@@ -268,12 +271,14 @@ export const useInstallOrUpdate = ({
|
||||
// has the same version, but not installed
|
||||
if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {
|
||||
return {
|
||||
success: true,
|
||||
status: TaskStatus.success,
|
||||
taskId: '',
|
||||
uniqueIdentifier: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isInstalled) {
|
||||
await post<InstallPackageResponse>('/workspaces/current/plugin/install/github', {
|
||||
const { task_id, all_installed } = await post<InstallPackageResponse>('/workspaces/current/plugin/install/github', {
|
||||
body: {
|
||||
repo: data.value.repo!,
|
||||
version: data.value.release! || data.value.version!,
|
||||
@@ -281,6 +286,8 @@ export const useInstallOrUpdate = ({
|
||||
plugin_unique_identifier: uniqueIdentifier,
|
||||
},
|
||||
})
|
||||
taskId = task_id
|
||||
isFinishedInstallation = all_installed
|
||||
}
|
||||
}
|
||||
if (item.type === 'marketplace') {
|
||||
@@ -288,15 +295,19 @@ export const useInstallOrUpdate = ({
|
||||
uniqueIdentifier = data.value.marketplace_plugin_unique_identifier! || plugin[i]?.plugin_id
|
||||
if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {
|
||||
return {
|
||||
success: true,
|
||||
status: TaskStatus.success,
|
||||
taskId: '',
|
||||
uniqueIdentifier: '',
|
||||
}
|
||||
}
|
||||
if (!isInstalled) {
|
||||
await post<InstallPackageResponse>('/workspaces/current/plugin/install/marketplace', {
|
||||
const { task_id, all_installed } = await post<InstallPackageResponse>('/workspaces/current/plugin/install/marketplace', {
|
||||
body: {
|
||||
plugin_unique_identifiers: [uniqueIdentifier],
|
||||
},
|
||||
})
|
||||
taskId = task_id
|
||||
isFinishedInstallation = all_installed
|
||||
}
|
||||
}
|
||||
if (item.type === 'package') {
|
||||
@@ -304,38 +315,59 @@ export const useInstallOrUpdate = ({
|
||||
uniqueIdentifier = data.value.unique_identifier
|
||||
if (uniqueIdentifier === installedPayload?.uniqueIdentifier) {
|
||||
return {
|
||||
success: true,
|
||||
status: TaskStatus.success,
|
||||
taskId: '',
|
||||
uniqueIdentifier: '',
|
||||
}
|
||||
}
|
||||
if (!isInstalled) {
|
||||
await post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
|
||||
const { task_id, all_installed } = await post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
|
||||
body: {
|
||||
plugin_unique_identifiers: [uniqueIdentifier],
|
||||
},
|
||||
})
|
||||
taskId = task_id
|
||||
isFinishedInstallation = all_installed
|
||||
}
|
||||
}
|
||||
if (isInstalled) {
|
||||
if (item.type === 'package') {
|
||||
await uninstallPlugin(installedPayload.installedId)
|
||||
await post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
|
||||
const { task_id, all_installed } = await post<InstallPackageResponse>('/workspaces/current/plugin/install/pkg', {
|
||||
body: {
|
||||
plugin_unique_identifiers: [uniqueIdentifier],
|
||||
},
|
||||
})
|
||||
taskId = task_id
|
||||
isFinishedInstallation = all_installed
|
||||
}
|
||||
else {
|
||||
await updatePackageFromMarketPlace({
|
||||
const { task_id, all_installed } = await updatePackageFromMarketPlace({
|
||||
original_plugin_unique_identifier: installedPayload?.uniqueIdentifier,
|
||||
new_plugin_unique_identifier: uniqueIdentifier,
|
||||
})
|
||||
taskId = task_id
|
||||
isFinishedInstallation = all_installed
|
||||
}
|
||||
}
|
||||
if (isFinishedInstallation) {
|
||||
return {
|
||||
status: TaskStatus.success,
|
||||
taskId: '',
|
||||
uniqueIdentifier: '',
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
status: TaskStatus.running,
|
||||
taskId,
|
||||
uniqueIdentifier,
|
||||
}
|
||||
}
|
||||
return ({ success: true })
|
||||
}
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
catch (e) {
|
||||
return Promise.resolve({ success: false })
|
||||
return Promise.resolve({ status: TaskStatus.failed, taskId: '', uniqueIdentifier: '' })
|
||||
}
|
||||
}))
|
||||
},
|
||||
|
||||
24
web/types/assets.d.ts
vendored
Normal file
24
web/types/assets.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
declare module '*.svg' {
|
||||
const value: any
|
||||
export default value
|
||||
}
|
||||
|
||||
declare module '*.png' {
|
||||
const value: any
|
||||
export default value
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const value: any
|
||||
export default value
|
||||
}
|
||||
|
||||
declare module '*.jpeg' {
|
||||
const value: any
|
||||
export default value
|
||||
}
|
||||
|
||||
declare module '*.gif' {
|
||||
const value: any
|
||||
export default value
|
||||
}
|
||||
Reference in New Issue
Block a user