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