Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev jobpage #265

Merged
merged 2 commits into from
Jan 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions solidui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

logger = logging.getLogger(__name__)


def create_app(solidui_config_module: Optional[str] = None) -> Flask:
app = SolidUIApp(__name__)
try:
Expand All @@ -41,7 +42,6 @@ def create_app(solidui_config_module: Optional[str] = None) -> Flask:
logger.exception("Failed to create app")
raise ex


class SolidUIApp(Flask):
pass


1 change: 1 addition & 0 deletions solidui/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://username:password@host:port/dbname'



# SQLALCHEMY_CUSTOM_PASSWORD_STORE = lookup_password
SQLALCHEMY_CUSTOM_PASSWORD_STORE = None

Expand Down
9 changes: 8 additions & 1 deletion solidui/daos/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

from datetime import datetime
from typing import Optional

from solidui.extensions import db

from solidui.daos.base import BaseDAO
from solidui.daos.exceptions import DAONotFound
from solidui.entity.core import Project
Expand Down Expand Up @@ -76,7 +79,11 @@ def get_project(cls, project_id: int) -> Project:
return project

@classmethod
def query_project_list_paging(cls, search_name: str, page_no: int, page_size: int) -> PageInfo[Project]:
def get_project_list(cls, status: int) -> list[Project]:
return db.session.query(Project).filter_by(status=status).all()

@classmethod
def get_project_list_paging(cls, search_name: str, page_no: int, page_size: int) -> PageInfo[Project]:
# Build custom filters
custom_filters = None
if search_name:
Expand Down
14 changes: 6 additions & 8 deletions solidui/initialization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import logging
import os
import sys
from typing import Any, Callable, TYPE_CHECKING
from typing import Any,TYPE_CHECKING
from deprecation import deprecated

import wtforms_json
Expand All @@ -27,14 +27,15 @@
appbuilder,
db
)
from solidui.solidui_typing import FlaskResponse
from solidui.utils.base import pessimistic_connection_handling, is_test


if TYPE_CHECKING:
from solidui.app import SolidUIApp

logger = logging.getLogger(__name__)


class SolidUIAppInitializer:
def __init__(self, app: SolidUIApp) -> None:
super().__init__()
Expand All @@ -48,7 +49,6 @@ def __init__(self, app: SolidUIApp) -> None:
def flask_app(self) -> SolidUIApp:
return self.solidui_app


def pre_init(self) -> None:
"""
Called before all other init tasks are complete
Expand All @@ -62,6 +62,8 @@ def post_init(self) -> None:
"""
Called after any other init tasks
"""
from solidui.views.base import schedule_clean_job_element_page
schedule_clean_job_element_page()

def setup_db(self) -> None:
db.init_app(self.solidui_app)
Expand Down Expand Up @@ -110,16 +112,12 @@ def init_views(self) -> None:
for rule in self.solidui_app.url_map.iter_rules():
print(rule)


def configure_fab(self) -> None:
if self.config["SILENCE_FAB"]:
logging.getLogger("flask_appbuilder").setLevel(logging.ERROR)


appbuilder.init_app(self.solidui_app, db.session)



def configure_session(self) -> None:
if self.config["SESSION_SERVER_SIDE"]:
Session(self.solidui_app)
Expand Down Expand Up @@ -175,6 +173,6 @@ def init_app(self) -> None:

with self.solidui_app.app_context():
self.init_app_in_ctx()
self.post_init()

self.post_init()

11 changes: 8 additions & 3 deletions solidui/utils/login_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from flask import request, make_response

from solidui.common.constants import SESSION_TIMEOUT, TICKETHEADER, CRYPTKEY, SESSION_TICKETID_KEY, ADMIN_NAME
from solidui.daos.exceptions import DAOException
from solidui.utils.des_utils import DESUtil

logger = logging.getLogger(__name__)
Expand All @@ -33,22 +34,26 @@ def remove_timeout_user_tickets():
if current_time - value > SESSION_TIMEOUT:
logger.info(f"remove timeout userTicket {key}, since the last access time is {value}.")
user_ticket_id_to_last_access_time.pop(key, None)
except Exception as e:
except DAOException as e:
logger.error("Failed to remove timeout user ticket id.", exc_info=True)

threads = threading.Timer(SESSION_TIMEOUT / 1000 / 10, remove_timeout_user_tickets)
threads.daemon = True
threads.start()

thread = threading.Timer(SESSION_TIMEOUT / 1000 / 10, remove_timeout_user_tickets)
thread.daemon = True
thread.start()


schedule_timeout_removal()


def get_user_ticket_id(username: str):
timeout_user = f"{username},{int(time.time() * 1000)}"
try:
return DESUtil.encrypt(f"{TICKETHEADER}{timeout_user}", CRYPTKEY)
except Exception as e:
except DAOException as e:
logger.info(f"Failed to encrypt user ticket id, username: {username}")
return None

Expand Down Expand Up @@ -85,6 +90,6 @@ def get_login_user(cookies):
timeout_user = DESUtil.decrypt(user_ticket_id, CRYPTKEY)
if timeout_user and timeout_user.startswith(TICKETHEADER):
return timeout_user.split(',')[0][len(TICKETHEADER):]
except Exception as e:
except DAOException as e:
logger.info(f"Failed to decrypt user ticket id, userTicketId: {user_ticket_id}")
return ADMIN_NAME
113 changes: 87 additions & 26 deletions solidui/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,51 +11,69 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import json
import logging
import threading
import time
from datetime import datetime

from dataclasses import asdict
from solidui.common.constants import CLEAN_PERIOD
from solidui.daos.exceptions import DAODeleteFailedError
from solidui.daos.job_element import JobElementDAO
from solidui.daos.job_element_page import JobElementPageDAO
from solidui.daos.job_page import JobPageDAO
from solidui.daos.project import ProjectDAO
from solidui.entity.core import JobElementPage, JobElement, JobPage
from solidui.views.base_schemas import View, DataView, Data, Size, Position, JobPageDTO
from solidui.views.base_schemas import View, DataView, Data, Size, Position, JobPageDTO, JobElementPageVO, Page

logger = logging.getLogger(__name__)


def deep_copy_view_to_data_view(view: View) -> DataView:
def serialize_dataclass(dataclass_instance):
"""
Serialize a dataclass instance to a dictionary, handling nested dataclasses.
"""
if isinstance(dataclass_instance, list):
return [serialize_dataclass(item) for item in dataclass_instance]
elif hasattr(dataclass_instance, "__dict__"):
return {k: serialize_dataclass(v) for k, v in asdict(dataclass_instance).items()}
else:
return dataclass_instance


def deep_copy_view_to_data_view(view: View):
# Copy Position
new_position = None
if view.position is not None:
new_position = Position(top=view.position.top, left=view.position.left)
new_position = Position(**view.position) if isinstance(view.position, dict) else view.position

# Copy Size
new_size = None
if view.size is not None:
new_size = Size(width=view.size.width, height=view.size.height)
new_size = Size(**view.size) if isinstance(view.size, dict) else view.size

# Copy Data
new_data = None
if view.data is not None:
new_data = Data(
dataSourceId=view.data.dataSourceId,
dataSourceName=view.data.dataSourceName,
dataSourceTypeId=view.data.dataSourceTypeId,
dataSourceTypeName=view.data.dataSourceTypeName,
sql=view.data.sql,
table=view.data.table
)
new_data = Data(**view.data) if isinstance(view.data, dict) else view.data

# Copy DataView
data_view = DataView(position=new_position, size=new_size, options=view.options, data=new_data)

return data_view
serialized_data_view = serialize_dataclass(data_view)

return json.dumps(serialized_data_view, ensure_ascii=False)


def save_job_element_page(job_element_page_vo: JobElementPageVO, job_element_id: int) -> None:
# page_id: int
new_page = Page(**job_element_page_vo.page) if isinstance(job_element_page_vo.page,
dict) else job_element_page_vo.page
# size: Size
new_size = Size(**job_element_page_vo.size) if isinstance(job_element_page_vo.size,
dict) else job_element_page_vo.size

serialized_size = serialize_dataclass(new_size)

def save_job_element_page(page_id: int, job_element_id: int, size: Size) -> None:
job_element_page = JobElementPage(
job_page_id=page_id,
job_page_id=new_page.id,
job_element_id=job_element_id,
position=json.dumps(size),
position=json.dumps(serialized_size, ensure_ascii=False),
create_time=datetime.now(),
update_time=datetime.now()
)
Expand All @@ -69,12 +87,19 @@ def create_job_element_page_vo(job_element: JobElement, views: list[View]) -> No
view = View()
view.id = job_element.id
view.title = job_element.name
view.type = job_element.dataType
view.type = job_element.data_type
# Assuming JSONUtils is replaced with a Python JSON library
data_view: DataView = json.loads(job_element.data)
if not data_view:
data_view_dict = json.loads(job_element.data)
if not data_view_dict:
return

data_view = DataView(
position=Position(**data_view_dict['position']) if 'position' in data_view_dict else None,
size=Size(**data_view_dict['size']) if 'size' in data_view_dict else None,
options=data_view_dict.get('options'),
data=Data(**data_view_dict['data']) if 'data' in data_view_dict else None
)

view.position = data_view.position
view.size = data_view.size
view.options = data_view.options
Expand All @@ -97,3 +122,39 @@ def convert_to_dto(job_page: JobPage):
children=[] # Initially empty, to be filled later if needed
)
return job_page_dto


def schedule_clean_job_element_page():
thread = threading.Timer(CLEAN_PERIOD / 1000 / 10, clean_job_element_page_task)
thread.daemon = True
thread.start()


def clean_job_element_page_task():
try:
clean_job_element_page()
except DAODeleteFailedError as e:
logger.error(f"Error cleaning job element page : {e}")
thread = threading.Timer(CLEAN_PERIOD / 1000 / 10, clean_job_element_page_task)
thread.daemon = True
thread.start()


def clean_job_element_page():
"""
Clean job element pages for projects.
"""
projects = ProjectDAO.get_project_list(1)
if projects:
for project in projects:
JobElementPageDAO.delete_project_id(project.id)
logger.info(f"cleanJobElementPage delete_project_id projectId: {project.id}")

JobElementDAO.delete_job_element_project_id(project.id)
logger.info(f"cleanJobElementPage delete_job_element_project_id projectId: {project.id}")
# Delete associated records
JobPageDAO.delete_project_id(project.id)
logger.info(f"cleanJobElementPage delete_project_id projectId: {project.id}")

ProjectDAO.delete(project)
logger.info(f"cleanJobElementPage projectId: {project.id}")
6 changes: 4 additions & 2 deletions solidui/views/base_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
from collections import deque
from dataclasses import dataclass
from datetime import datetime
from typing import Optional, Dict, List

from typing import Dict, List

from marshmallow_sqlalchemy.schema import Schema
from marshmallow_sqlalchemy.fields import fields


@dataclass
class Position:
top: str = None
Expand Down Expand Up @@ -130,3 +130,5 @@ class JobPageDTOSchema(Schema):
orders = fields.Int()
# Use a Nested field for children, with many=True to indicate it's a list
children = fields.Nested('self', many=True, exclude=('children',))


Loading
Loading