diff --git a/applications/osb-portal/src/components/dialogs/DeleteDialog.tsx b/applications/osb-portal/src/components/dialogs/DeleteDialog.tsx new file mode 100644 index 00000000..477e2c89 --- /dev/null +++ b/applications/osb-portal/src/components/dialogs/DeleteDialog.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import DialogActions from "@mui/material/DialogActions"; +import Dialog from "@mui/material/Dialog"; +import { + useNavigate, +} from "react-router-dom"; +import { Button } from "@mui/material"; + + +const DeleteDialog = ({ + open, + setOpen, + workspace, + handleDeleteWorkspace, +}) => { + const navigate = useNavigate(); + + const handleDelete = () => { + handleDeleteWorkspace(); + if (window.location.pathname !== "/") { + navigate("/"); + } + } + + return ( + setOpen(false)} + > + {'Delete Workspace "' + workspace.name + '"'} + {'You are about to delete Workspace "' + workspace.name + '". This action cannot be undone. Are you sure?'} + + + + + + ) +} + +export default DeleteDialog; \ No newline at end of file diff --git a/applications/osb-portal/src/components/workspace/WorkspaceActionsMenu.tsx b/applications/osb-portal/src/components/workspace/WorkspaceActionsMenu.tsx index 7484b913..b0cb8f91 100644 --- a/applications/osb-portal/src/components/workspace/WorkspaceActionsMenu.tsx +++ b/applications/osb-portal/src/components/workspace/WorkspaceActionsMenu.tsx @@ -18,6 +18,7 @@ import OSBLoader from "../common/OSBLoader"; import { bgDarkest, textColor, lightWhite } from "../../theme"; import MoreHorizIcon from "@mui/icons-material/MoreHoriz"; import * as Icons from "../icons"; +import DeleteDialog from "../dialogs/DeleteDialog"; interface WorkspaceActionsMenuProps { @@ -51,6 +52,7 @@ export default (props: WorkspaceActionsMenuProps) => { const [anchorEl, setAnchorEl] = React.useState(null); const canEdit = canEditWorkspace(props?.user, props?.workspace); const navigate = useNavigate(); + const [showDeleteWorkspaceDialog, setShowDeleteWorkspaceDialog] = React.useState(false); const handleClick = (event: React.MouseEvent) => { @@ -210,7 +212,10 @@ export default (props: WorkspaceActionsMenuProps) => { {canEdit && ( { + setShowDeleteWorkspaceDialog(true); + setAnchorEl(null); + }} > Delete @@ -261,6 +266,18 @@ export default (props: WorkspaceActionsMenuProps) => { } /> + <> + { + showDeleteWorkspaceDialog && ( + + ) + } + ); }; diff --git a/applications/osb-portal/src/pages/WorkspacesPage.tsx b/applications/osb-portal/src/pages/WorkspacesPage.tsx index 8b6bae0f..da0a9454 100644 --- a/applications/osb-portal/src/pages/WorkspacesPage.tsx +++ b/applications/osb-portal/src/pages/WorkspacesPage.tsx @@ -88,9 +88,6 @@ export const WorkspacesPage = (props: WorkspacesPageProps) => { setSearchFilterValues({ ...searchFilterValues, text: newTextFilter, - tags: newTextFilter - ? [...searchFilterValues?.tags, newTextFilter] - : searchFilterValues?.tags, }); }, 500); diff --git a/applications/osb-portal/src/service/RepositoryService.tsx b/applications/osb-portal/src/service/RepositoryService.tsx index 016eda6b..7d78be45 100644 --- a/applications/osb-portal/src/service/RepositoryService.tsx +++ b/applications/osb-portal/src/service/RepositoryService.tsx @@ -18,7 +18,7 @@ type RepositoriesListAndPaginationDetails = InlineResponse2001; const workspacesApiUri = "/proxy/workspaces/api"; -const PER_PAGE_DEFAULT = 18; +const PER_PAGE_DEFAULT = 24; class RepositoryService { workspacesApi: RestApi = null; @@ -108,7 +108,7 @@ class RepositoryService { return this.workspacesApi.osbrepositoryGet({ page, perPage: size, - q: `user_id=${userId}`, + userId: userId }); } diff --git a/applications/osb-portal/src/service/WorkspaceService.tsx b/applications/osb-portal/src/service/WorkspaceService.tsx index ef27ab0e..b6edbc91 100644 --- a/applications/osb-portal/src/service/WorkspaceService.tsx +++ b/applications/osb-portal/src/service/WorkspaceService.tsx @@ -124,6 +124,7 @@ class WorkspaceService { if (filter.text) { params.name__like = filter.text; + params.description__like = filter.text; } if (filter.user_id) { diff --git a/applications/workspaces/server/workspaces/persistence/crud_persistence.py b/applications/workspaces/server/workspaces/persistence/crud_persistence.py index 97e2378e..aa835687 100644 --- a/applications/workspaces/server/workspaces/persistence/crud_persistence.py +++ b/applications/workspaces/server/workspaces/persistence/crud_persistence.py @@ -8,7 +8,7 @@ from cloudharness import log as logger from cloudharness.service import pvc -from sqlalchemy import desc, or_ +from sqlalchemy import desc, or_, and_ from sqlalchemy.sql import func @@ -52,36 +52,69 @@ def check(self): def search_qs(self, filter=None, q=None, tags=None, user_id=None, show_all=False, *args, **kwargs): q_base = self.model.query + q_base = self.filter_by_user_and_fieldkey( + filter, user_id, show_all, q_base) + if filter is None: + if tags: + q_base = self.filter_by_tags(tags, q_base) + else: + q_base_by_name_description = self.filter_by_name_description( + filter, q_base) + q_base = self.filter_by_search_tags( + filter, q_base, q_base_by_name_description) + + if tags: + q_base = q_base.intersect(self.filter_by_tags(tags, q_base)) + + q_base = self.filter_by_publicable_and_featured(filter, q_base) + + return q_base.order_by(desc(WorkspaceEntity.timestamp_updated)) + + def filter_by_publicable_and_featured(self, filter, q_base): + q_base = q_base.filter( + and_(*[self._create_filter(*f) for f in filter if (f[0].key == "publicable" or f[0].key == "featured")])) + return q_base + + def filter_by_user_and_fieldkey(self, filter, user_id, show_all, q_base): if filter and any(field for field, condition, value in filter if field.key == "publicable" and value): pass elif user_id is not None: # Admins see all workspaces, non admin users can see only their own workspaces or shared with them if not show_all: q_base = q_base.filter_by(user_id=user_id) - q_base = q_base.union(q_base.filter( + q_base = q_base.union(self.model.query.filter( WorkspaceEntity.collaborators.any(user_id=user_id))) else: q_base = q_base else: # No logged in user, show only public (in case was not specified) q_base = q_base.filter(WorkspaceEntity.publicable == True) + return q_base + def filter_by_tags(self, tags, q_base): + q_base = q_base.join(self.model.tags).filter( + func.lower(Tag.tag).in_(func.lower(t) for t in tags.split("+"))) + return q_base - if filter is not None: - if tags: - q_base = q_base.filter( - *[self._create_filter(*f) for f in filter if f[0].key == "name"] ) - q_base = q_base.intersect(self.model.query.join(self.model.tags).filter( - func.lower(Tag.tag).in_(func.lower(t) for t in tags.split("+")))) - - q_base = q_base.filter( - *[self._create_filter(*f) for f in filter if f[0].key != "name"]) - elif tags: - q_base = q_base.join(self.model.tags).filter( - func.lower(Tag.tag).in_(func.lower(t) for t in tags.split("+"))) + def filter_by_search_tags(self, filter, q_base, q_base_by_name_description): + search_tags = self.tags_from_search(filter) + q_base = q_base_by_name_description.union(q_base.join(self.model.tags).filter( + func.lower(Tag.tag).in_(func.lower(t) for t in search_tags.split("+")))) - return q_base.order_by(desc(WorkspaceEntity.timestamp_updated)) + return q_base + + def tags_from_search(self, filter): + tags = '' + for field, condition, value in filter: + if field.key == "name": + tags = value.replace("+", "").replace("%", "") + break + return tags + + def filter_by_name_description(self, filter, q_base): + return q_base.filter( + or_(*[self._create_filter(*f) for f in filter if (f[0].key == "name" or f[0].key == "description")])) def delete(self, id): super().delete(id) @@ -100,21 +133,48 @@ def get(self, id): def search_qs(self, filter=None, q=None, tags=None, types=None, user_id=None): q_base = self.model.query - if tags: - q_base = q_base.join(self.model.tags).filter( - Tag.tag.in_(tags.split("+"))) - if filter: - q_base = q_base.filter( - or_(*[self._create_filter(*f) for f in filter])) + if user_id is not None: + q_base = q_base.filter_by(user_id=user_id) + + if filter is None: + if tags: + q_base = self.filter_by_tags(tags, q_base) + else: + q_base_by_q = self.filter_by_qfilters(filter, q_base) + q_base = self.filter_by_search_tags(filter, q_base, q_base_by_q) + if tags: + q_base = q_base.intersect(self.filter_by_tags(tags, q_base)) if types is not None: q_base = q_base.filter( or_(self.model.content_types.ilike(f"%{t}%") for t in types.split("+"))) - if user_id is not None: - q_base = q_base.filter_by(user_id=user_id) return q_base.order_by(desc(OSBRepositoryEntity.timestamp_updated)) + def filter_by_qfilters(self, filter, q_base): + q_base = q_base.filter( + or_(*[self._create_filter(*f) for f in filter])) + return q_base + + def filter_by_tags(self, tags, q_base): + q_base = q_base.join(self.model.tags).filter( + func.lower(Tag.tag).in_(func.lower(t) for t in tags.split("+"))) + return q_base + + def filter_by_search_tags(self, filter, q_base, q_base_by_q): + search_tags = self.tags_from_search(filter) + q_base = q_base_by_q.union(q_base.join(self.model.tags).filter( + func.lower(Tag.tag).in_(func.lower(t) for t in search_tags.split("+")))) + return q_base + + def tags_from_search(self, filter): + tags = '' + for field, condition, value in filter: + if field.key == "name": + tags = value.replace("+", "").replace("%", "") + break + return tags + class VolumeStorageRepository(BaseModelRepository): model = VolumeStorage