mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-12 02:49:03 +08:00
Fix/notion sync (#1258)
This commit is contained in:
parent
bcd744b6b7
commit
c9b0fe47bf
@ -45,15 +45,34 @@ class OAuthDataSource(Resource):
|
||||
if current_app.config.get('NOTION_INTEGRATION_TYPE') == 'internal':
|
||||
internal_secret = current_app.config.get('NOTION_INTERNAL_SECRET')
|
||||
oauth_provider.save_internal_access_token(internal_secret)
|
||||
return redirect(f'{current_app.config.get("CONSOLE_WEB_URL")}?oauth_data_source=success')
|
||||
return { 'data': '' }
|
||||
else:
|
||||
auth_url = oauth_provider.get_authorization_url()
|
||||
return redirect(auth_url)
|
||||
return { 'data': auth_url }, 200
|
||||
|
||||
|
||||
|
||||
|
||||
class OAuthDataSourceCallback(Resource):
|
||||
def get(self, provider: str):
|
||||
OAUTH_DATASOURCE_PROVIDERS = get_oauth_providers()
|
||||
with current_app.app_context():
|
||||
oauth_provider = OAUTH_DATASOURCE_PROVIDERS.get(provider)
|
||||
if not oauth_provider:
|
||||
return {'error': 'Invalid provider'}, 400
|
||||
if 'code' in request.args:
|
||||
code = request.args.get('code')
|
||||
|
||||
return redirect(f'{current_app.config.get("CONSOLE_WEB_URL")}?type=notion&code={code}')
|
||||
elif 'error' in request.args:
|
||||
error = request.args.get('error')
|
||||
|
||||
return redirect(f'{current_app.config.get("CONSOLE_WEB_URL")}?type=notion&error={error}')
|
||||
else:
|
||||
return redirect(f'{current_app.config.get("CONSOLE_WEB_URL")}?type=notion&error=Access denied')
|
||||
|
||||
|
||||
class OAuthDataSourceBinding(Resource):
|
||||
def get(self, provider: str):
|
||||
OAUTH_DATASOURCE_PROVIDERS = get_oauth_providers()
|
||||
with current_app.app_context():
|
||||
@ -69,12 +88,7 @@ class OAuthDataSourceCallback(Resource):
|
||||
f"An error occurred during the OAuthCallback process with {provider}: {e.response.text}")
|
||||
return {'error': 'OAuth data source process failed'}, 400
|
||||
|
||||
return redirect(f'{current_app.config.get("CONSOLE_WEB_URL")}?oauth_data_source=success')
|
||||
elif 'error' in request.args:
|
||||
error = request.args.get('error')
|
||||
return redirect(f'{current_app.config.get("CONSOLE_WEB_URL")}?oauth_data_source={error}')
|
||||
else:
|
||||
return redirect(f'{current_app.config.get("CONSOLE_WEB_URL")}?oauth_data_source=access_denied')
|
||||
return {'result': 'success'}, 200
|
||||
|
||||
|
||||
class OAuthDataSourceSync(Resource):
|
||||
@ -101,4 +115,5 @@ class OAuthDataSourceSync(Resource):
|
||||
|
||||
api.add_resource(OAuthDataSource, '/oauth/data-source/<string:provider>')
|
||||
api.add_resource(OAuthDataSourceCallback, '/oauth/data-source/callback/<string:provider>')
|
||||
api.add_resource(OAuthDataSourceBinding, '/oauth/data-source/binding/<string:provider>')
|
||||
api.add_resource(OAuthDataSourceSync, '/oauth/data-source/<string:provider>/<uuid:binding_id>/sync')
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Link from 'next/link'
|
||||
import { PlusIcon } from '@heroicons/react/24/solid'
|
||||
import cn from 'classnames'
|
||||
import Indicator from '../../../indicator'
|
||||
import Operate from './operate'
|
||||
import s from './style.module.css'
|
||||
import NotionIcon from '@/app/components/base/notion-icon'
|
||||
import { apiPrefix } from '@/config'
|
||||
import type { DataSourceNotion as TDataSourceNotion } from '@/models/common'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { fetchNotionConnection } from '@/service/common'
|
||||
|
||||
type DataSourceNotionProps = {
|
||||
workspaces: TDataSourceNotion[]
|
||||
@ -18,9 +19,30 @@ const DataSourceNotion = ({
|
||||
}: DataSourceNotionProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
const [canConnectNotion, setCanConnectNotion] = useState(false)
|
||||
const { data } = useSWR(canConnectNotion ? '/oauth/data-source/notion' : null, fetchNotionConnection)
|
||||
|
||||
const connected = !!workspaces.length
|
||||
|
||||
const handleConnectNotion = () => {
|
||||
if (!isCurrentWorkspaceManager)
|
||||
return
|
||||
|
||||
setCanConnectNotion(true)
|
||||
}
|
||||
|
||||
const handleAuthAgain = () => {
|
||||
if (data?.data)
|
||||
window.location.href = data.data
|
||||
else
|
||||
setCanConnectNotion(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.data)
|
||||
window.location.href = data.data
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<div className='mb-2 border-[0.5px] border-gray-200 bg-gray-50 rounded-xl'>
|
||||
<div className='flex items-center px-3 py-[9px]'>
|
||||
@ -40,26 +62,28 @@ const DataSourceNotion = ({
|
||||
{
|
||||
connected
|
||||
? (
|
||||
<Link
|
||||
<div
|
||||
className={
|
||||
`flex items-center ml-3 px-3 h-7 bg-white border border-gray-200
|
||||
rounded-md text-xs font-medium text-gray-700
|
||||
${isCurrentWorkspaceManager ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
|
||||
}
|
||||
href={isCurrentWorkspaceManager ? `${apiPrefix}/oauth/data-source/notion` : '/'}>
|
||||
onClick={handleConnectNotion}
|
||||
>
|
||||
{t('common.dataSource.connect')}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<Link
|
||||
href={isCurrentWorkspaceManager ? `${apiPrefix}/oauth/data-source/notion` : '/' }
|
||||
<div
|
||||
className={
|
||||
`flex items-center px-3 h-7 bg-white border-[0.5px] border-gray-200 text-xs font-medium text-primary-600 rounded-md
|
||||
${isCurrentWorkspaceManager ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
|
||||
}>
|
||||
}
|
||||
onClick={handleConnectNotion}
|
||||
>
|
||||
<PlusIcon className='w-[14px] h-[14px] mr-[5px]' />
|
||||
{t('common.dataSource.notion.addWorkspace')}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
@ -98,7 +122,7 @@ const DataSourceNotion = ({
|
||||
}
|
||||
</div>
|
||||
<div className='mr-2 w-[1px] h-3 bg-gray-100' />
|
||||
<Operate workspace={workspace} />
|
||||
<Operate workspace={workspace} onAuthAgain={handleAuthAgain} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
'use client'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Fragment } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import { EllipsisHorizontalIcon } from '@heroicons/react/24/solid'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import { apiPrefix } from '@/config'
|
||||
import { syncDataSourceNotion, updateDataSourceNotionAction } from '@/service/common'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { DataSourceNotion } from '@/models/common'
|
||||
@ -15,9 +13,11 @@ import { Trash03 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
type OperateProps = {
|
||||
workspace: DataSourceNotion
|
||||
onAuthAgain: () => void
|
||||
}
|
||||
export default function Operate({
|
||||
workspace,
|
||||
onAuthAgain,
|
||||
}: OperateProps) {
|
||||
const itemClassName = `
|
||||
flex px-3 py-2 hover:bg-gray-50 text-sm text-gray-700
|
||||
@ -71,9 +71,10 @@ export default function Operate({
|
||||
>
|
||||
<div className="px-1 py-1">
|
||||
<Menu.Item>
|
||||
<Link
|
||||
<div
|
||||
className={itemClassName}
|
||||
href={`${apiPrefix}/oauth/data-source/notion`}>
|
||||
onClick={onAuthAgain}
|
||||
>
|
||||
<FilePlus02 className={itemIconClassName} />
|
||||
<div>
|
||||
<div className='leading-5'>{t('common.dataSource.notion.changeAuthorizedPages')}</div>
|
||||
@ -81,7 +82,7 @@ export default function Operate({
|
||||
{workspace.source_info.total} {t('common.dataSource.notion.pagesAuthorized')}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<div className={itemClassName} onClick={handleSync}>
|
||||
|
@ -7,7 +7,10 @@ import useSWR from 'swr'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import I18n from '@/context/i18n'
|
||||
import { ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
|
||||
import { fetchFreeQuotaVerify } from '@/service/common'
|
||||
import {
|
||||
fetchDataSourceNotionBinding,
|
||||
fetchFreeQuotaVerify,
|
||||
} from '@/service/common'
|
||||
import type { ConfirmCommonProps } from '@/app/components/base/confirm/common'
|
||||
import Confirm from '@/app/components/base/confirm/common'
|
||||
|
||||
@ -92,19 +95,56 @@ export const useCheckFreeQuota = () => {
|
||||
: null
|
||||
}
|
||||
|
||||
export const useCheckNotion = () => {
|
||||
const router = useRouter()
|
||||
const [confirm, setConfirm] = useState<ConfirmType | null>(null)
|
||||
const [canBinding, setCanBinding] = useState(false)
|
||||
const searchParams = useSearchParams()
|
||||
const type = searchParams.get('type')
|
||||
const notionCode = searchParams.get('code')
|
||||
const notionError = searchParams.get('error')
|
||||
const { data } = useSWR(
|
||||
canBinding
|
||||
? `/oauth/data-source/binding/notion?code=${notionCode}`
|
||||
: null,
|
||||
fetchDataSourceNotionBinding,
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (data)
|
||||
router.replace('/', { forceOptimisticNavigation: false })
|
||||
}, [data, router])
|
||||
useEffect(() => {
|
||||
if (type === 'notion') {
|
||||
if (notionError) {
|
||||
setConfirm({
|
||||
type: 'danger',
|
||||
title: notionError,
|
||||
})
|
||||
}
|
||||
else if (notionCode) {
|
||||
setCanBinding(true)
|
||||
}
|
||||
}
|
||||
}, [type, notionCode, notionError])
|
||||
|
||||
return confirm
|
||||
}
|
||||
|
||||
export const CheckModal = () => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation()
|
||||
const [showPayStatusModal, setShowPayStatusModal] = useState(true)
|
||||
const anthropicConfirmInfo = useAnthropicCheckPay()
|
||||
const freeQuotaConfirmInfo = useCheckFreeQuota()
|
||||
const notionConfirmInfo = useCheckNotion()
|
||||
|
||||
const handleCancelShowPayStatusModal = useCallback(() => {
|
||||
setShowPayStatusModal(false)
|
||||
router.replace('/', { forceOptimisticNavigation: false })
|
||||
}, [router])
|
||||
|
||||
const confirmInfo = anthropicConfirmInfo || freeQuotaConfirmInfo
|
||||
const confirmInfo = anthropicConfirmInfo || freeQuotaConfirmInfo || notionConfirmInfo
|
||||
|
||||
if (!confirmInfo || !showPayStatusModal)
|
||||
return null
|
||||
|
@ -188,3 +188,11 @@ export const fetchDocumentsLimit: Fetcher<DocumentsLimitResponse, string> = (url
|
||||
export const fetchFreeQuotaVerify: Fetcher<{ result: string; flag: boolean; reason: string }, string> = (url) => {
|
||||
return get(url) as Promise<{ result: string; flag: boolean; reason: string }>
|
||||
}
|
||||
|
||||
export const fetchNotionConnection: Fetcher<{ data: string }, string> = (url) => {
|
||||
return get(url) as Promise<{ data: string }>
|
||||
}
|
||||
|
||||
export const fetchDataSourceNotionBinding: Fetcher<{ result: string }, string> = (url) => {
|
||||
return get(url) as Promise<{ result: string }>
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user