Skip to content

Commit

Permalink
tasks code and task history displayed in app panel (#360)
Browse files Browse the repository at this point in the history
* task docstring, code and run histroy added

* task result fields modified, docstring field removed

* ADD : Task History added

* Add : Code container changed to monaco editor

* Fix:Task History card updated

* Fix : line breaks fixed at task history card

* Add : card text made overflow x -axis

* Fix : task coloumn title changes bold to normal weight

* Delete: comment deleted

* FIX : task name text color changed

* FIX : Mock data changes done

* fix lint errors

* FIX: 1. Monoco editor made only readable 2. 'No task history' if no data task history

* Fix : revert changes from main

* Add : task history modal additional added  "name, id "

* Fix : start_date issue fixed

* FIX : removed "remove policy button"

* extra workspace creation removed

* build removed

* Fix : build files removed

* Remove : unwanted console removed

* revert build changes

---------

Co-authored-by: prem056627 <prem@zelthy.com>
Co-authored-by: Harsh Shah <shahharsh176@gmail.com>
  • Loading branch information
3 people authored Sep 11, 2024
1 parent 78599ee commit acee998
Show file tree
Hide file tree
Showing 18 changed files with 453 additions and 68 deletions.
44 changes: 43 additions & 1 deletion backend/src/zango/api/platform/tasks/v1/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import inspect
import json

from django_celery_beat.models import CrontabSchedule
from django_celery_beat.models import CrontabSchedule, PeriodicTask
from django_celery_results.models import TaskResult
from rest_framework import serializers

from zango.api.platform.permissions.v1.serializers import PolicySerializer
Expand All @@ -24,6 +26,8 @@ class TaskSerializer(serializers.ModelSerializer):
attached_policies = PolicySerializer(many=True)
crontab = CronTabSerializer()
schedule = serializers.SerializerMethodField()
code = serializers.SerializerMethodField()
run_history = serializers.SerializerMethodField()

class Meta:
model = AppTask
Expand All @@ -42,3 +46,41 @@ def update(self, instance, validated_data):

def get_schedule(self, obj):
return str(obj.crontab)[:-18]

def get_code(self, obj):
try:
md = self.context.get("plugin_source").load_plugin(
obj.name[: obj.name.rfind(".")]
)
task = getattr(md, obj.name[obj.name.rfind(".") + 1 :])
code = inspect.getsource(task)
return code
except Exception:
return ""

def get_run_history(self, obj):
if not self.context.get("history"):
return []
ptask = PeriodicTask.objects.get(id=obj.master_task_id)
serializer = TaskResultSerializer(
TaskResult.objects.filter(periodic_task_name=ptask.name).order_by(
"-date_done"
),
many=True,
)
return serializer.data


class TaskResultSerializer(serializers.ModelSerializer):
date_started = serializers.SerializerMethodField()
date_done = serializers.SerializerMethodField()

class Meta:
model = TaskResult
fields = ["date_started", "date_done", "result", "traceback"]

def get_date_started(self, obj):
return obj.date_done.strftime("%Y-%m-%d %H:%M:%S")

def get_date_done(self, obj):
return obj.date_done.strftime("%Y-%m-%d %H:%M:%S")
2 changes: 1 addition & 1 deletion backend/src/zango/api/platform/tasks/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@

urlpatterns = [
path("", AppTaskView.as_view()),
re_path(r"(?P<task_uuid>[\w-]+)/", AppTaskDetailView.as_view()),
re_path(r"(?P<task_id>\d+)/", AppTaskDetailView.as_view()),
]
28 changes: 21 additions & 7 deletions backend/src/zango/api/platform/tasks/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def get_queryset(self, search, columns={}):
columns["is_enabled"] = True
if columns.get("is_enabled") == "false":
columns["is_enabled"] = False
records = AppTask.objects.all().order_by("-id")
records = AppTask.objects.filter(is_deleted=False).order_by("-id")
if search == "" and columns == {}:
return records
filters = Q()
Expand All @@ -50,7 +50,15 @@ def get(self, request, app_uuid, task_uuid=None, *args, **kwargs):
columns = get_search_columns(request)
app_tasks = self.get_queryset(search, columns)
paginated_tasks = self.paginate_queryset(app_tasks, request, view=self)
serializer = TaskSerializer(paginated_tasks, many=True)
tenant = TenantModel.objects.get(uuid=app_uuid)
connection.set_tenant(tenant)
with connection.cursor() as c:
ws = Workspace.get_plugin_source()
serializer = TaskSerializer(
paginated_tasks,
many=True,
context={"plugin_source": ws},
)
paginated_app_tasks = self.get_paginated_response_data(serializer.data)
success = True
response = {
Expand Down Expand Up @@ -85,10 +93,16 @@ def post(self, request, app_uuid, *args, **kwargs):

@method_decorator(set_app_schema_path, name="dispatch")
class AppTaskDetailView(ZangoGenericPlatformAPIView):
def get(self, request, app_uuid, task_uuid, *args, **kwargs):
def get(self, request, app_uuid, task_id, *args, **kwargs):
try:
app_task = AppTask.objects.get(id=task_uuid)
serializer = TaskSerializer(instance=app_task)
app_task = AppTask.objects.get(id=task_id)
tenant = TenantModel.objects.get(uuid=app_uuid)
connection.set_tenant(tenant)
with connection.cursor() as c:
serializer = TaskSerializer(
instance=app_task,
context={"history": True},
)
response = {"task": serializer.data}
status = 200
success = True
Expand All @@ -98,11 +112,11 @@ def get(self, request, app_uuid, task_uuid, *args, **kwargs):
success = False
return get_api_response(success, response, status)

def post(self, request, app_uuid, task_uuid, *args, **kwargs):
def post(self, request, app_uuid, task_id, *args, **kwargs):
data = request.data
crontab_exp = data.get("crontab_exp")
try:
app_task = AppTask.objects.get(id=task_uuid)
app_task = AppTask.objects.get(id=task_id)
serializer = TaskSerializer(
instance=app_task,
data=request.data,
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion backend/src/zango/assets/js/jquery/3.7.1/jquery.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions backend/src/zango/config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@

# Celery
CELERY_RESULT_BACKEND = "django-db"
CELERY_RESULT_EXTENDED = True
X_FRAME_OPTIONS = "ALLOW"

PACKAGE_BUCKET_NAME = "zelthy3-packages"
Expand Down
64 changes: 64 additions & 0 deletions frontend/src/components/Table/HeaderInfoHover.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Menu, Transition } from '@headlessui/react';
import { Fragment, useState } from 'react';
import { usePopper } from 'react-popper';
import { ReactComponent as InfoIcon } from '../../../src/assets/images/svg/info-icon.svg';

export default function HeaderInfoHover({ message }) {
const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
const [isOpen, setIsOpen] = useState(false);

const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: 'bottom-end',
modifiers: [
{
name: 'offset',
options: {
offset: [460, 2],
},
},
],
});

return (
<div
className="relative"
onMouseEnter={() => setIsOpen(true)}
onMouseLeave={() => setIsOpen(false)}
>
<Menu as="div" className="relative flex">
<Transition
as={Fragment}
ref={(ref) => setPopperElement(ref)}
style={styles.popper}
{...attributes.popper}
show={isOpen}
>
<Menu.Items className="absolute bottom-[30px] right-0 z-20 min-w-[186px] origin-bottom-right rounded-[4px] bg-white shadow-table-menu focus:outline-none">
<div className="p-[4px]">
<Menu.Item>
{({ active }) => (
<div
className={`${
active ? 'bg-white' : ''
} flex w-full max-w-fit flex-col rounded-[2px] px-[12px] py-[8px]`}
>
<span className="text-wrap text-start font-lato text-[12px] leading-[16px] tracking-[0.2px] text-[#6C747D]">
{message}
</span>
</div>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
<Menu.Button
className="relative z-10 flex w-full justify-center focus:outline-none"
ref={(ref) => setReferenceElement(ref)}
>
<InfoIcon className="h-[16px] min-h-[16px] w-[16px] min-w-[16px] " />
</Menu.Button>
</Menu>
</div>
);
}
2 changes: 1 addition & 1 deletion frontend/src/metadata.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ "buildMajor": 0, "buildMinor": 3, "buildPatch": 0, "buildTag": "" }
{ "buildMajor": 0, "buildMinor": 3, "buildPatch": 0, "buildTag": "" }
110 changes: 110 additions & 0 deletions frontend/src/mocks/appTasksManagementHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ const newTask = () => {
last_login: faker.date.past(),
is_enabled: faker.datatype.boolean(),
created_at: faker.date.past(),
docstring: '\n This is a docstring\n ',
code: '@shared_task()\ndef test():\n """\n This is a docstring\n """\n print("test")\n return "test"\n',
};
};

Expand Down Expand Up @@ -99,6 +101,7 @@ export const appTasksManagementHandlers = [
return res(
ctx.delay(500),
ctx.status(200),

ctx.json({
success: true,
response: {
Expand All @@ -123,6 +126,113 @@ export const appTasksManagementHandlers = [
);
}),

rest.get('/api/v1/apps/:appId/tasks/:taskId', (req, res, ctx) => {
return res(
ctx.delay(500),
ctx.status(200),
ctx.json({
success: true,
response: {
task: {
id: 2,
attached_policies: [],
crontab: {
minute: '*',
hour: '*',
day_of_week: '*',
day_of_month: '*',
month_of_year: '*',
},
schedule: '* * * * *',
docstring: '',
code: '',
run_history: [
{
date_started: '2024-08-12T07:30:00.043275Z',
date_done: '2024-08-12T07:30:00.043294Z',
result: '"Task completed successfully"',
traceback: null,
},
{
date_started: '2024-08-12T07:29:00.039694Z',
date_done: '2024-08-12T07:29:00.039726Z',
result: '"Data processed"',
traceback:
'Traceback(mostrecentalllast):Filetask.py,line4,inprocess_datadatafetch_dTraceback(mostrecent call last):\n File "task.py", line 34, in process_data\n data = fetch_dTraceback (most recent call last):\n File "task.py", line 34, in process_data\n data = fetch_dTraceback (most recent call last):\n File "task.py", line 34, in process_data\n data = fetch_dTraceback (most recent call last):\n File "task.py", line 34, in process_data\n data = fetch_dTraceback (most recent call last):Filetask.py", line 34, in process_data\n data = fetch_dTraceback (most recent call last):\n File "task.py", line 34, in process_data\n data = fetch_dTraceback (most recent call last):\n File "task.py", line 34, in process_data\n data = fetch_data()\nKeyError: \'id\'',
},
{
date_started: '2024-08-12T07:28:00.141579Z',
date_done: '2024-08-12T07:28:00.141628Z',
result: '"Export failed"',
traceback:
'TraceThisisaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocsspropertiesappliedtowraporbreakwordsappropriatelysuchtextcanoverfloworbreaklayoutsinunexpectedsituationsback (most recent call last):\n File "task.py", line 22, in export_data\n export_to_csv(data)\nFileNotFoundError: [Errno 2] No such file or directory: \'output.csv\'',
},
{
date_started: '2024-08-12T07:27:00.141579Z',
date_done: '2024-08-12T07:27:00.141628Z',
result: '"Email sent"',
traceback: null,
},
{
date_started: '2024-08-12T07:26:00.141579Z',
date_done: '2024-08-12T07:26:00.141628Z',
result: '"Database update failed"',
traceback:
'TracThisisaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocsspropertiesappliedtowraporbreakwordsappropriatelysuchtextcanoverfloworbreaklayoutsinunexpectedsituationseback (most recent call last):\n File "task.py", line 45, in update_database\n cursor.execute(query)\npsycopg2.IntegrityError: duplicate key value violates unique constraint "users_pkey"',
},
{
date_started: '2024-08-12T07:25:00.141579Z',
date_done: '2024-08-12T07:25:00.141628Z',
result: '"Backup completed with warnings"',
traceback:
'Traceback (most recent call last):\n File "backup.py", line 10, in perform_backup\n raise Warning(\'Low disk space\')\nWarning: Low disk space',
},
{
date_started: '2024-08-12T07:24:00.141579Z',
date_done: '2024-08-12T07:24:00.141628Z',
result: '"Report generation failed"',
traceback:
'Traceback (most recent call last):\n FiThisisaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocssproaplaceholderparagraphdesignedtoshowwhattextwithoutspaceslookslikewhenitisrenderedinawebpagewithnocsspropertiesappliedtowraporbreakwordsappropriatelysuchtextcanoverfloworbreaklayoutsinunexpectedsituationsle "report.py", line 59, in generate_report\n report = create_report(data)\nValueError: Missing required field \'report_name\'',
},
{
date_started: '2024-08-12T07:23:00.141579Z',
date_done: '2024-08-12T07:23:00.141628Z',
result: '"Task completed with warnings"',
traceback:
'Traceback (most recent call last):\n File "task.py", line 78, in execute_task\n validate_input(input_data)\nDeprecationWarning: \'validate_input\' is deprecated and will be removed in future versions',
},
{
date_started: '2024-08-12T07:22:00.141579Z',
date_done: '2024-08-12T07:22:00.141628Z',
result: '"Task failed"',
traceback:
'Traceback (most recent call last):\n File "task.py", line 11, in perform_task\n run_process()\nTypeError: \'NoneType\' object is not callable',
},
{
date_started: '2024-08-12T07:21:00.141579Z',
date_done: '2024-08-12T07:21:00.141628Z',
result: '"File upload failed"',
traceback:
'Traceback (most recent call last):\n File "upload.py", line 27, in upload_file\n response = requests.post(url, files=files)\nrequests.exceptions.ConnectionError: Failed to establish a new connection',
},
],

created_at: '2024-08-12T07:22:14.716445Z',
created_by: '',
modified_at: '2024-08-12T07:30:21.674722Z',
modified_by: '',
name: 'accounts.tasks.test',
is_enabled: false,
is_deleted: true,
args: '["CRMApp2", "accounts.tasks.test"]',
kwargs: {},
interval: null,
master_task: 52,
},
},
})
);
}),
rest.post('/api/v1/apps/:appId/tasks/', (req, res, ctx) => {
const action = req.url.searchParams.get('action');
if (action === 'sync_tasks') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createColumnHelper } from '@tanstack/react-table';
import { find } from 'lodash';
import moment from 'moment';
import HeaderInfo from '../../../../components/Table/HeaderInfo';

import ListGeneralCell from '../../../../components/Table/ListGeneralCell';
import TableDateRangeFilter from '../../../../components/Table/TableDateRangeFilter';
import TableDropdownFilter from '../../../../components/Table/TableDropdownFilter';
Expand Down Expand Up @@ -146,7 +147,7 @@ function columns({ debounceSearch, localTableData, tableData }) {
id: 'actor',
header: () => (
<div className="flex h-full items-start justify-start gap-[10px] whitespace-nowrap border-b-[4px] border-[#F0F3F4] px-[20px] py-[12px] text-start">
<span className="font-lato text-[11px] font-bold uppercase leading-[16px] tracking-[0.6px] text-[#6C747D]">
<span className="font-lato text-[11px] font-bold uppercase leading-[16px] tracking-[0.6px] text-[#6C747D] ">
Actor
</span>
<HeaderInfo message={'* Denotes Platform User'} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,11 @@ export default function RowMenu({ rowData }) {
</Menu.Button>
<Transition
as={Fragment}
// @ts-ignore
ref={(ref) => setPopperElement(ref)}
style={styles['popper']}
{...attributes['popper']}
>
<Menu.Items className="absolute top-[30px] right-0 w-[186px] origin-top-right rounded-[4px] bg-white shadow-table-menu focus:outline-none">
<Menu.Items className="absolute right-0 top-[30px] w-[186px] origin-top-right rounded-[4px] bg-white shadow-table-menu focus:outline-none">
<div className="p-[4px]">
{rowData?.status === 'Installed' ? null : (
<Menu.Item>
Expand All @@ -72,9 +71,6 @@ export default function RowMenu({ rowData }) {
<span className="text-start font-lato text-[14px] font-bold leading-[20px] tracking-[0.2px] text-[#212429]">
Install Package
</span>
{/* <span className="text-start font-lato text-[12px] leading-[16px] tracking-[0.2px] text-[#6C747D]">
install package
</span> */}
</div>
</button>
)}
Expand Down
Loading

0 comments on commit acee998

Please sign in to comment.