Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
27e8b067
Commit
27e8b067
authored
Feb 27, 2024
by
takatost
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add workflow app log api
parent
0e8fbd3a
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
276 additions
and
7 deletions
+276
-7
__init__.py
api/controllers/console/__init__.py
+1
-1
app.py
api/controllers/console/app/app.py
+2
-2
workflow.py
api/controllers/console/app/workflow.py
+36
-0
workflow_app_log.py
api/controllers/console/app/workflow_app_log.py
+41
-0
end_user_fields.py
api/fields/end_user_fields.py
+8
-0
workflow_app_log_fields.py
api/fields/workflow_app_log_fields.py
+25
-0
workflow_fields.py
api/fields/workflow_fields.py
+13
-0
__init__.py
api/models/__init__.py
+44
-1
workflow.py
api/models/workflow.py
+19
-1
app_service.py
api/services/app_service.py
+2
-1
workflow_app_service.py
api/services/workflow_app_service.py
+62
-0
workflow_service.py
api/services/workflow_service.py
+23
-1
No files found.
api/controllers/console/__init__.py
View file @
27e8b067
...
...
@@ -8,7 +8,7 @@ api = ExternalApi(bp)
from
.
import
admin
,
apikey
,
extension
,
feature
,
setup
,
version
,
ping
# Import app controllers
from
.app
import
(
advanced_prompt_template
,
annotation
,
app
,
audio
,
completion
,
conversation
,
generator
,
message
,
model_config
,
site
,
statistic
,
workflow
)
model_config
,
site
,
statistic
,
workflow
,
workflow_app_log
)
# Import auth controllers
from
.auth
import
activate
,
data_source_oauth
,
login
,
oauth
# Import billing controllers
...
...
api/controllers/console/app/app.py
View file @
27e8b067
...
...
@@ -35,9 +35,9 @@ class AppListApi(Resource):
# get app list
app_service
=
AppService
()
app_
models
=
app_service
.
get_paginate_apps
(
current_user
.
current_tenant_id
,
args
)
app_
pagination
=
app_service
.
get_paginate_apps
(
current_user
.
current_tenant_id
,
args
)
return
app_
models
return
app_
pagination
@
setup_required
@
login_required
...
...
api/controllers/console/app/workflow.py
View file @
27e8b067
...
...
@@ -51,6 +51,41 @@ class DraftWorkflowApi(Resource):
}
class
PublishedWorkflowApi
(
Resource
):
@
setup_required
@
login_required
@
account_initialization_required
@
get_app_model
(
mode
=
[
AppMode
.
ADVANCED_CHAT
,
AppMode
.
WORKFLOW
])
@
marshal_with
(
workflow_fields
)
def
get
(
self
,
app_model
:
App
):
"""
Get published workflow
"""
# fetch published workflow by app_model
workflow_service
=
WorkflowService
()
workflow
=
workflow_service
.
get_published_workflow
(
app_model
=
app_model
)
# return workflow, if not found, return None
return
workflow
@
setup_required
@
login_required
@
account_initialization_required
@
get_app_model
(
mode
=
[
AppMode
.
ADVANCED_CHAT
,
AppMode
.
WORKFLOW
])
def
post
(
self
,
app_model
:
App
):
"""
Publish workflow
"""
workflow_service
=
WorkflowService
()
workflow_service
.
publish_workflow
(
app_model
=
app_model
,
account
=
current_user
)
return
{
"result"
:
"success"
}
class
DefaultBlockConfigApi
(
Resource
):
@
setup_required
@
login_required
...
...
@@ -88,5 +123,6 @@ class ConvertToWorkflowApi(Resource):
api
.
add_resource
(
DraftWorkflowApi
,
'/apps/<uuid:app_id>/workflows/draft'
)
api
.
add_resource
(
PublishedWorkflowApi
,
'/apps/<uuid:app_id>/workflows/published'
)
api
.
add_resource
(
DefaultBlockConfigApi
,
'/apps/<uuid:app_id>/workflows/default-workflow-block-configs'
)
api
.
add_resource
(
ConvertToWorkflowApi
,
'/apps/<uuid:app_id>/convert-to-workflow'
)
api/controllers/console/app/workflow_app_log.py
0 → 100644
View file @
27e8b067
from
flask_restful
import
Resource
,
marshal_with
,
reqparse
from
flask_restful.inputs
import
int_range
from
controllers.console
import
api
from
controllers.console.app.wraps
import
get_app_model
from
controllers.console.setup
import
setup_required
from
controllers.console.wraps
import
account_initialization_required
from
fields.workflow_app_log_fields
import
workflow_app_log_pagination_fields
from
libs.login
import
login_required
from
models.model
import
AppMode
,
App
from
services.workflow_app_service
import
WorkflowAppService
class
WorkflowAppLogApi
(
Resource
):
@
setup_required
@
login_required
@
account_initialization_required
@
get_app_model
(
mode
=
[
AppMode
.
WORKFLOW
])
@
marshal_with
(
workflow_app_log_pagination_fields
)
def
get
(
self
,
app_model
:
App
):
"""
Get workflow app logs
"""
parser
=
reqparse
.
RequestParser
()
parser
.
add_argument
(
'keyword'
,
type
=
str
,
location
=
'args'
)
parser
.
add_argument
(
'status'
,
type
=
str
,
choices
=
[
'succeeded'
,
'failed'
,
'stopped'
],
location
=
'args'
)
parser
.
add_argument
(
'page'
,
type
=
int_range
(
1
,
99999
),
default
=
1
,
location
=
'args'
)
parser
.
add_argument
(
'limit'
,
type
=
int_range
(
1
,
100
),
default
=
20
,
location
=
'args'
)
args
=
parser
.
parse_args
()
# get paginate workflow app logs
workflow_app_service
=
WorkflowAppService
()
workflow_app_log_pagination
=
workflow_app_service
.
get_paginate_workflow_app_logs
(
app_model
=
app_model
,
args
=
args
)
return
workflow_app_log_pagination
api
.
add_resource
(
WorkflowAppLogApi
,
'/apps/<uuid:app_id>/workflow-app-logs'
)
api/fields/end_user_fields.py
0 → 100644
View file @
27e8b067
from
flask_restful
import
fields
simple_end_user_fields
=
{
'id'
:
fields
.
String
,
'type'
:
fields
.
String
,
'is_anonymous'
:
fields
.
Boolean
,
'session_id'
:
fields
.
String
,
}
api/fields/workflow_app_log_fields.py
0 → 100644
View file @
27e8b067
from
flask_restful
import
fields
from
fields.end_user_fields
import
simple_end_user_fields
from
fields.member_fields
import
simple_account_fields
from
fields.workflow_fields
import
workflow_run_fields
from
libs.helper
import
TimestampField
workflow_app_log_partial_fields
=
{
"id"
:
fields
.
String
,
"workflow_run"
:
fields
.
Nested
(
workflow_run_fields
,
attribute
=
'workflow_run'
,
allow_null
=
True
),
"created_from"
:
fields
.
String
,
"created_by_role"
:
fields
.
String
,
"created_by_account"
:
fields
.
Nested
(
simple_account_fields
,
attribute
=
'created_by_account'
,
allow_null
=
True
),
"created_by_end_user"
:
fields
.
Nested
(
simple_end_user_fields
,
attribute
=
'created_by_end_user'
,
allow_null
=
True
),
"created_at"
:
TimestampField
}
workflow_app_log_pagination_fields
=
{
'page'
:
fields
.
Integer
,
'limit'
:
fields
.
Integer
(
attribute
=
'per_page'
),
'total'
:
fields
.
Integer
,
'has_more'
:
fields
.
Boolean
(
attribute
=
'has_next'
),
'data'
:
fields
.
List
(
fields
.
Nested
(
workflow_app_log_partial_fields
),
attribute
=
'items'
)
}
api/fields/workflow_fields.py
View file @
27e8b067
...
...
@@ -13,3 +13,16 @@ workflow_fields = {
'updated_by'
:
fields
.
Nested
(
simple_account_fields
,
attribute
=
'updated_by_account'
,
allow_null
=
True
),
'updated_at'
:
TimestampField
}
workflow_run_fields
=
{
"id"
:
fields
.
String
,
"version"
:
fields
.
String
,
"status"
:
fields
.
String
,
"error"
:
fields
.
String
,
"elapsed_time"
:
fields
.
Float
,
"total_tokens"
:
fields
.
Integer
,
"total_price"
:
fields
.
Float
,
"currency"
:
fields
.
String
,
"total_steps"
:
fields
.
Integer
,
"finished_at"
:
TimestampField
}
\ No newline at end of file
api/models/__init__.py
View file @
27e8b067
# -*- coding:utf-8 -*-
\ No newline at end of file
from
enum
import
Enum
class
CreatedByRole
(
Enum
):
"""
Enum class for createdByRole
"""
ACCOUNT
=
"account"
END_USER
=
"end_user"
@
classmethod
def
value_of
(
cls
,
value
:
str
)
->
'CreatedByRole'
:
"""
Get value of given mode.
:param value: mode value
:return: mode
"""
for
role
in
cls
:
if
role
.
value
==
value
:
return
role
raise
ValueError
(
f
'invalid createdByRole value {value}'
)
class
CreatedFrom
(
Enum
):
"""
Enum class for createdFrom
"""
SERVICE_API
=
"service-api"
WEB_APP
=
"web-app"
EXPLORE
=
"explore"
@
classmethod
def
value_of
(
cls
,
value
:
str
)
->
'CreatedFrom'
:
"""
Get value of given mode.
:param value: mode value
:return: mode
"""
for
role
in
cls
:
if
role
.
value
==
value
:
return
role
raise
ValueError
(
f
'invalid createdFrom value {value}'
)
api/models/workflow.py
View file @
27e8b067
...
...
@@ -5,6 +5,7 @@ from sqlalchemy.dialects.postgresql import UUID
from
extensions.ext_database
import
db
from
models.account
import
Account
from
models.model
import
EndUser
class
CreatedByRole
(
Enum
):
...
...
@@ -148,6 +149,7 @@ class WorkflowRunStatus(Enum):
RUNNING
=
'running'
SUCCEEDED
=
'succeeded'
FAILED
=
'failed'
STOPPED
=
'stopped'
@
classmethod
def
value_of
(
cls
,
value
:
str
)
->
'WorkflowRunStatus'
:
...
...
@@ -184,7 +186,7 @@ class WorkflowRun(db.Model):
- version (string) Version
- graph (text) Workflow canvas configuration (JSON)
- inputs (text) Input parameters
- status (string) Execution status, `running` / `succeeded` / `failed`
- status (string) Execution status, `running` / `succeeded` / `failed`
/ `stopped`
- outputs (text) `optional` Output content
- error (string) `optional` Error reason
- elapsed_time (float) `optional` Time consumption (s)
...
...
@@ -366,3 +368,19 @@ class WorkflowAppLog(db.Model):
created_by_role
=
db
.
Column
(
db
.
String
(
255
),
nullable
=
False
)
created_by
=
db
.
Column
(
UUID
,
nullable
=
False
)
created_at
=
db
.
Column
(
db
.
DateTime
,
nullable
=
False
,
server_default
=
db
.
text
(
'CURRENT_TIMESTAMP(0)'
))
@
property
def
workflow_run
(
self
):
return
WorkflowRun
.
query
.
get
(
self
.
workflow_run_id
)
@
property
def
created_by_account
(
self
):
created_by_role
=
CreatedByRole
.
value_of
(
self
.
created_by_role
)
return
Account
.
query
.
get
(
self
.
created_by
)
\
if
created_by_role
==
CreatedByRole
.
ACCOUNT
else
None
@
property
def
created_by_end_user
(
self
):
created_by_role
=
CreatedByRole
.
value_of
(
self
.
created_by_role
)
return
EndUser
.
query
.
get
(
self
.
created_by
)
\
if
created_by_role
==
CreatedByRole
.
END_USER
else
None
api/services/app_service.py
View file @
27e8b067
...
...
@@ -3,6 +3,7 @@ from datetime import datetime
from
typing
import
cast
import
yaml
from
flask_sqlalchemy.pagination
import
Pagination
from
constants.model_template
import
default_app_templates
from
core.errors.error
import
ProviderTokenNotInitError
...
...
@@ -17,7 +18,7 @@ from services.workflow_service import WorkflowService
class
AppService
:
def
get_paginate_apps
(
self
,
tenant_id
:
str
,
args
:
dict
)
->
list
[
App
]
:
def
get_paginate_apps
(
self
,
tenant_id
:
str
,
args
:
dict
)
->
Pagination
:
"""
Get app list with pagination
:param tenant_id: tenant id
...
...
api/services/workflow_app_service.py
0 → 100644
View file @
27e8b067
from
flask_sqlalchemy.pagination
import
Pagination
from
sqlalchemy
import
or_
,
and_
from
extensions.ext_database
import
db
from
models
import
CreatedByRole
from
models.model
import
App
,
EndUser
from
models.workflow
import
WorkflowAppLog
,
WorkflowRunStatus
,
WorkflowRun
class
WorkflowAppService
:
def
get_paginate_workflow_app_logs
(
self
,
app_model
:
App
,
args
:
dict
)
->
Pagination
:
"""
Get paginate workflow app logs
:param app: app model
:param args: request args
:return:
"""
query
=
(
db
.
select
(
WorkflowAppLog
)
.
where
(
WorkflowAppLog
.
tenant_id
==
app_model
.
tenant_id
,
WorkflowAppLog
.
app_id
==
app_model
.
id
)
)
status
=
WorkflowRunStatus
.
value_of
(
args
.
get
(
'status'
))
if
args
.
get
(
'status'
)
else
None
if
args
[
'keyword'
]
or
status
:
query
=
query
.
join
(
WorkflowRun
,
WorkflowRun
.
id
==
WorkflowAppLog
.
workflow_run_id
)
if
args
[
'keyword'
]:
keyword_val
=
f
"
%
{args['keyword'][:30]}
%
"
keyword_conditions
=
[
WorkflowRun
.
inputs
.
ilike
(
keyword_val
),
WorkflowRun
.
outputs
.
ilike
(
keyword_val
),
# filter keyword by end user session id if created by end user role
and_
(
WorkflowRun
.
created_by_role
==
'end_user'
,
EndUser
.
session_id
.
ilike
(
keyword_val
))
]
query
=
query
.
outerjoin
(
EndUser
,
and_
(
WorkflowRun
.
created_by
==
EndUser
.
id
,
WorkflowRun
.
created_by_role
==
CreatedByRole
.
END_USER
.
value
)
)
.
filter
(
or_
(
*
keyword_conditions
))
if
status
:
# join with workflow_run and filter by status
query
=
query
.
filter
(
WorkflowRun
.
status
==
status
.
value
)
query
=
query
.
order_by
(
WorkflowAppLog
.
created_at
.
desc
())
pagination
=
db
.
paginate
(
query
,
page
=
args
[
'page'
],
per_page
=
args
[
'limit'
],
error_out
=
False
)
return
pagination
api/services/workflow_service.py
View file @
27e8b067
...
...
@@ -15,7 +15,7 @@ class WorkflowService:
Workflow Service
"""
def
get_draft_workflow
(
self
,
app_model
:
App
)
->
Workflow
:
def
get_draft_workflow
(
self
,
app_model
:
App
)
->
Optional
[
Workflow
]
:
"""
Get draft workflow
"""
...
...
@@ -29,6 +29,26 @@ class WorkflowService:
# return draft workflow
return
workflow
def
get_published_workflow
(
self
,
app_model
:
App
)
->
Optional
[
Workflow
]:
"""
Get published workflow
"""
app_model_config
=
app_model
.
app_model_config
if
not
app_model_config
.
workflow_id
:
return
None
# fetch published workflow by workflow_id
workflow
=
db
.
session
.
query
(
Workflow
)
.
filter
(
Workflow
.
tenant_id
==
app_model
.
tenant_id
,
Workflow
.
app_id
==
app_model
.
id
,
Workflow
.
id
==
app_model_config
.
workflow_id
)
.
first
()
# return published workflow
return
workflow
def
sync_draft_workflow
(
self
,
app_model
:
App
,
graph
:
dict
,
account
:
Account
)
->
Workflow
:
"""
Sync draft workflow
...
...
@@ -116,6 +136,8 @@ class WorkflowService:
app_model
.
app_model_config_id
=
new_app_model_config
.
id
db
.
session
.
commit
()
# TODO update app related datasets
# return new workflow
return
workflow
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment