feat: Check and compare the DSL version before import an app (#10969)
Co-authored-by: Yi <yxiaoisme@gmail.com>
This commit is contained in:
parent
d9579f418d
commit
5172f0bf39
24 changed files with 1101 additions and 874 deletions
|
|
@ -12,9 +12,13 @@ import Input from '@/app/components/base/input'
|
|||
import Modal from '@/app/components/base/modal'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import {
|
||||
importApp,
|
||||
importAppFromUrl,
|
||||
importDSL,
|
||||
importDSLConfirm,
|
||||
} from '@/service/apps'
|
||||
import {
|
||||
DSLImportMode,
|
||||
DSLImportStatus,
|
||||
} from '@/models/app'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||
|
|
@ -43,6 +47,9 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
|||
const [fileContent, setFileContent] = useState<string>()
|
||||
const [currentTab, setCurrentTab] = useState(activeTab)
|
||||
const [dslUrlValue, setDslUrlValue] = useState(dslUrl)
|
||||
const [showErrorModal, setShowErrorModal] = useState(false)
|
||||
const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>()
|
||||
const [importId, setImportId] = useState<string>()
|
||||
|
||||
const readFile = (file: File) => {
|
||||
const reader = new FileReader()
|
||||
|
|
@ -66,6 +73,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
|||
const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
|
||||
|
||||
const isCreatingRef = useRef(false)
|
||||
|
||||
const onCreate: MouseEventHandler = async () => {
|
||||
if (currentTab === CreateFromDSLModalTab.FROM_FILE && !currentFile)
|
||||
return
|
||||
|
|
@ -75,25 +83,54 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
|||
return
|
||||
isCreatingRef.current = true
|
||||
try {
|
||||
let app
|
||||
let response
|
||||
|
||||
if (currentTab === CreateFromDSLModalTab.FROM_FILE) {
|
||||
app = await importApp({
|
||||
data: fileContent || '',
|
||||
response = await importDSL({
|
||||
mode: DSLImportMode.YAML_CONTENT,
|
||||
yaml_content: fileContent || '',
|
||||
})
|
||||
}
|
||||
if (currentTab === CreateFromDSLModalTab.FROM_URL) {
|
||||
app = await importAppFromUrl({
|
||||
url: dslUrlValue || '',
|
||||
response = await importDSL({
|
||||
mode: DSLImportMode.YAML_URL,
|
||||
yaml_url: dslUrlValue || '',
|
||||
})
|
||||
}
|
||||
if (onSuccess)
|
||||
onSuccess()
|
||||
if (onClose)
|
||||
onClose()
|
||||
notify({ type: 'success', message: t('app.newApp.appCreated') })
|
||||
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
|
||||
getRedirection(isCurrentWorkspaceEditor, app, push)
|
||||
|
||||
if (!response)
|
||||
return
|
||||
|
||||
const { id, status, app_id, imported_dsl_version, current_dsl_version } = response
|
||||
if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) {
|
||||
if (onSuccess)
|
||||
onSuccess()
|
||||
if (onClose)
|
||||
onClose()
|
||||
|
||||
notify({
|
||||
type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning',
|
||||
message: t(status === DSLImportStatus.COMPLETED ? 'app.newApp.appCreated' : 'app.newApp.caution'),
|
||||
children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('app.newApp.appCreateDSLWarning'),
|
||||
})
|
||||
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
|
||||
getRedirection(isCurrentWorkspaceEditor, { id: app_id }, push)
|
||||
}
|
||||
else if (status === DSLImportStatus.PENDING) {
|
||||
setVersions({
|
||||
importedVersion: imported_dsl_version ?? '',
|
||||
systemVersion: current_dsl_version ?? '',
|
||||
})
|
||||
if (onClose)
|
||||
onClose()
|
||||
setTimeout(() => {
|
||||
setShowErrorModal(true)
|
||||
}, 300)
|
||||
setImportId(id)
|
||||
}
|
||||
else {
|
||||
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
||||
|
|
@ -101,6 +138,38 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
|||
isCreatingRef.current = false
|
||||
}
|
||||
|
||||
const onDSLConfirm: MouseEventHandler = async () => {
|
||||
try {
|
||||
if (!importId)
|
||||
return
|
||||
const response = await importDSLConfirm({
|
||||
import_id: importId,
|
||||
})
|
||||
|
||||
const { status, app_id } = response
|
||||
|
||||
if (status === DSLImportStatus.COMPLETED) {
|
||||
if (onSuccess)
|
||||
onSuccess()
|
||||
if (onClose)
|
||||
onClose()
|
||||
|
||||
notify({
|
||||
type: 'success',
|
||||
message: t('app.newApp.appCreated'),
|
||||
})
|
||||
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
|
||||
getRedirection(isCurrentWorkspaceEditor, { id: app_id }, push)
|
||||
}
|
||||
else if (status === DSLImportStatus.FAILED) {
|
||||
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
||||
}
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
key: CreateFromDSLModalTab.FROM_FILE,
|
||||
|
|
@ -123,74 +192,96 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
|||
}, [isAppsFull, currentTab, currentFile, dslUrlValue])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className='p-0 w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'
|
||||
isShow={show}
|
||||
onClose={() => { }}
|
||||
>
|
||||
<div className='flex items-center justify-between pt-6 pl-6 pr-5 pb-3 text-text-primary title-2xl-semi-bold'>
|
||||
{t('app.importFromDSL')}
|
||||
<div
|
||||
className='flex items-center w-8 h-8 cursor-pointer'
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
<RiCloseLine className='w-5 h-5 text-text-tertiary' />
|
||||
<>
|
||||
<Modal
|
||||
className='p-0 w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'
|
||||
isShow={show}
|
||||
onClose={() => { }}
|
||||
>
|
||||
<div className='flex items-center justify-between pt-6 pl-6 pr-5 pb-3 text-text-primary title-2xl-semi-bold'>
|
||||
{t('app.importFromDSL')}
|
||||
<div
|
||||
className='flex items-center w-8 h-8 cursor-pointer'
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
<RiCloseLine className='w-5 h-5 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center px-6 h-9 space-x-6 system-md-semibold text-text-tertiary border-b border-divider-subtle'>
|
||||
{
|
||||
tabs.map(tab => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={cn(
|
||||
'relative flex items-center h-full cursor-pointer',
|
||||
currentTab === tab.key && 'text-text-primary',
|
||||
)}
|
||||
onClick={() => setCurrentTab(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
{
|
||||
currentTab === tab.key && (
|
||||
<div className='absolute bottom-0 w-full h-[2px] bg-util-colors-blue-brand-blue-brand-600'></div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div className='px-6 py-4'>
|
||||
{
|
||||
currentTab === CreateFromDSLModalTab.FROM_FILE && (
|
||||
<Uploader
|
||||
className='mt-0'
|
||||
file={currentFile}
|
||||
updateFile={handleFile}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
currentTab === CreateFromDSLModalTab.FROM_URL && (
|
||||
<div>
|
||||
<div className='mb-1 system-md-semibold leading6'>DSL URL</div>
|
||||
<Input
|
||||
placeholder={t('app.importFromDSLUrlPlaceholder') || ''}
|
||||
value={dslUrlValue}
|
||||
onChange={e => setDslUrlValue(e.target.value)}
|
||||
<div className='flex items-center px-6 h-9 space-x-6 system-md-semibold text-text-tertiary border-b border-divider-subtle'>
|
||||
{
|
||||
tabs.map(tab => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={cn(
|
||||
'relative flex items-center h-full cursor-pointer',
|
||||
currentTab === tab.key && 'text-text-primary',
|
||||
)}
|
||||
onClick={() => setCurrentTab(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
{
|
||||
currentTab === tab.key && (
|
||||
<div className='absolute bottom-0 w-full h-[2px] bg-util-colors-blue-brand-blue-brand-600'></div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div className='px-6 py-4'>
|
||||
{
|
||||
currentTab === CreateFromDSLModalTab.FROM_FILE && (
|
||||
<Uploader
|
||||
className='mt-0'
|
||||
file={currentFile}
|
||||
updateFile={handleFile}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{isAppsFull && (
|
||||
<div className='px-6'>
|
||||
<AppsFull className='mt-0' loc='app-create-dsl' />
|
||||
)
|
||||
}
|
||||
{
|
||||
currentTab === CreateFromDSLModalTab.FROM_URL && (
|
||||
<div>
|
||||
<div className='mb-1 system-md-semibold leading6'>DSL URL</div>
|
||||
<Input
|
||||
placeholder={t('app.importFromDSLUrlPlaceholder') || ''}
|
||||
value={dslUrlValue}
|
||||
onChange={e => setDslUrlValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
<div className='flex justify-end px-6 py-5'>
|
||||
<Button className='mr-2' onClick={onClose}>{t('app.newApp.Cancel')}</Button>
|
||||
<Button disabled={buttonDisabled} variant="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
{isAppsFull && (
|
||||
<div className='px-6'>
|
||||
<AppsFull className='mt-0' loc='app-create-dsl' />
|
||||
</div>
|
||||
)}
|
||||
<div className='flex justify-end px-6 py-5'>
|
||||
<Button className='mr-2' onClick={onClose}>{t('app.newApp.Cancel')}</Button>
|
||||
<Button disabled={buttonDisabled} variant="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal
|
||||
isShow={showErrorModal}
|
||||
onClose={() => setShowErrorModal(false)}
|
||||
className='w-[480px]'
|
||||
>
|
||||
<div className='flex pb-4 flex-col items-start gap-2 self-stretch'>
|
||||
<div className='text-text-primary title-2xl-semi-bold'>{t('app.newApp.appCreateDSLErrorTitle')}</div>
|
||||
<div className='flex flex-grow flex-col text-text-secondary system-md-regular'>
|
||||
<div>{t('app.newApp.appCreateDSLErrorPart1')}</div>
|
||||
<div>{t('app.newApp.appCreateDSLErrorPart2')}</div>
|
||||
<br />
|
||||
<div>{t('app.newApp.appCreateDSLErrorPart3')}<span className='system-md-medium'>{versions?.importedVersion}</span></div>
|
||||
<div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions?.systemVersion}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex pt-6 justify-end items-start gap-2 self-stretch'>
|
||||
<Button variant='secondary' onClick={() => setShowErrorModal(false)}>{t('app.newApp.Cancel')}</Button>
|
||||
<Button variant='primary' destructive onClick={onDSLConfirm}>{t('app.newApp.Confirm')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { formatFileSize } from '@/utils/format'
|
||||
import cn from '@/utils/classnames'
|
||||
import { Yaml as YamlIcon } from '@/app/components/base/icons/src/public/files'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
|
|
@ -58,8 +59,13 @@ const Uploader: FC<Props> = ({
|
|||
updateFile(files[0])
|
||||
}
|
||||
const selectHandle = () => {
|
||||
if (fileUploader.current)
|
||||
const originalFile = file
|
||||
if (fileUploader.current) {
|
||||
fileUploader.current.value = ''
|
||||
fileUploader.current.click()
|
||||
// If no file is selected, restore the original file
|
||||
fileUploader.current.oncancel = () => updateFile(originalFile)
|
||||
}
|
||||
}
|
||||
const removeFile = () => {
|
||||
if (fileUploader.current)
|
||||
|
|
@ -96,7 +102,7 @@ const Uploader: FC<Props> = ({
|
|||
/>
|
||||
<div ref={dropRef}>
|
||||
{!file && (
|
||||
<div className={cn('flex items-center h-20 rounded-xl bg-gray-50 border border-dashed border-gray-200 text-sm font-normal', dragging && 'bg-[#F5F8FF] border border-[#B2CCFF]')}>
|
||||
<div className={cn('flex items-center h-12 rounded-xl bg-gray-50 border border-dashed border-gray-200 text-sm font-normal', dragging && 'bg-[#F5F8FF] border border-[#B2CCFF]')}>
|
||||
<div className='w-full flex items-center justify-center space-x-2'>
|
||||
<UploadCloud01 className='w-6 h-6 mr-2' />
|
||||
<div className='text-gray-500'>
|
||||
|
|
@ -108,17 +114,23 @@ const Uploader: FC<Props> = ({
|
|||
</div>
|
||||
)}
|
||||
{file && (
|
||||
<div className={cn('flex items-center h-20 px-6 rounded-xl bg-gray-50 border border-gray-200 text-sm font-normal group', 'hover:bg-[#F5F8FF] hover:border-[#B2CCFF]')}>
|
||||
<YamlIcon className="shrink-0" />
|
||||
<div className='flex ml-2 w-0 grow'>
|
||||
<span className='max-w-[calc(100%_-_30px)] text-ellipsis whitespace-nowrap overflow-hidden text-gray-800'>{file.name.replace(/(.yaml|.yml)$/, '')}</span>
|
||||
<span className='shrink-0 text-gray-500'>.yml</span>
|
||||
<div className={cn('flex items-center rounded-lg bg-components-panel-on-panel-item-bg border-[0.5px] border-components-panel-border shadow-xs group', 'hover:bg-[#F5F8FF] hover:border-[#B2CCFF]')}>
|
||||
<div className='flex p-3 justify-center items-center'>
|
||||
<YamlIcon className="w-6 h-6 shrink-0" />
|
||||
</div>
|
||||
<div className='flex py-1 pr-2 grow flex-col items-start gap-0.5'>
|
||||
<span className='max-w-[calc(100%_-_30px)] text-ellipsis whitespace-nowrap overflow-hidden text-text-secondary font-inter text-[12px] font-medium leading-4'>{file.name}</span>
|
||||
<div className='flex h-3 items-center gap-1 self-stretch text-text-tertiary font-inter text-[10px] font-medium leading-3 uppercase'>
|
||||
<span>YAML</span>
|
||||
<span className='text-text-quaternary'>·</span>
|
||||
<span>{formatFileSize(file.size)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='hidden group-hover:flex items-center'>
|
||||
<Button onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.change')}</Button>
|
||||
<div className='mx-2 w-px h-4 bg-gray-200' />
|
||||
<div className='p-2 cursor-pointer' onClick={removeFile}>
|
||||
<RiDeleteBinLine className='w-4 h-4 text-gray-500' />
|
||||
<RiDeleteBinLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue