Skip to content

Commit

Permalink
Merge pull request #286 from VineetBala-AOT/develop
Browse files Browse the repository at this point in the history
Adding the EAO documents page
  • Loading branch information
VineetBala-AOT authored Feb 10, 2025
2 parents a534b98 + 3a64324 commit 510b076
Show file tree
Hide file tree
Showing 13 changed files with 492 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ class AccountProjectSearchOptions: # pylint: disable=too-many-instance-attribut
status: List[PackageStatus] # Update to be a list of PackageStatus
submitted_on_start: str
submitted_on_end: str

@dataclass
class DocumentSearchOptions: # pylint: disable=too-many-instance-attributes
"""Used to store document search options."""

search_text: str
80 changes: 80 additions & 0 deletions submit-api/src/submit_api/models/queries/submitted_document.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Copyright © 2024 Province of British Columbia
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.
"""Model to handle all complex operations related to Submitted Documents."""

from sqlalchemy import cast, or_, String
from submit_api.models.account_project import AccountProject
from submit_api.models.account_project_search_options import DocumentSearchOptions
from submit_api.models.db import db
from submit_api.models.item import Item
from submit_api.models.package import Package
from submit_api.models.project import Project
from submit_api.models.submission import Submission, SubmissionType
from submit_api.models.submitted_document import SubmittedDocument


class DocumentQueries:
"""Query module for complex document queries."""

@classmethod
def get_filtered_documents(cls, search_options: DocumentSearchOptions = None):
"""Get all documents."""
session = db.session

query = session.query(
Project.name.label("project_name"),
SubmittedDocument.id,
SubmittedDocument.name,
SubmittedDocument.url,
Item.status,
# TODO once document version is captured this logic needs to be revisited
(cast(Submission.major_version, String) + "." +
cast(Submission.minor_version, String)).label("version"),
Package.submitted_on
)

# Apply joins step by step
query = query.join(AccountProject, AccountProject.project_id == Project.id)
query = query.join(
Package, Package.account_project_id == AccountProject.id)
query = query.join(Item, Item.package_id == Package.id)
query = query.join(Submission, Submission.item_id == Item.id)
query = query.join(
SubmittedDocument, SubmittedDocument.id == Submission.submitted_document_id)

# Apply filtering conditions
query = query.filter(Submission.type == SubmissionType.DOCUMENT)

# Apply search filters if provided
if search_options and any(bool(search_option) for search_option in search_options.__dict__.values()):
query = cls.filter_by_search_criteria(query, search_options)

return query.all()

@classmethod
def filter_by_search_criteria(cls, document_query, search_options: DocumentSearchOptions):
"""Apply various filters based on search options."""

if search_options.search_text:
document_query = cls._filter_by_search_text(document_query, search_options.search_text)

@classmethod
def _filter_by_search_text(cls, query, search_text):
"""Filter by search text across package name."""
return query.filter(
or_(
Package.name.ilike(f"%{search_text}%"),
Project.name.ilike(f"%{search_text}%")
)
)
2 changes: 2 additions & 0 deletions submit-api/src/submit_api/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from .staff.staff_user import API as STAFF_USER_API
from .staff.submission_item_note import API as STAFF_SUBMISSION_ITEM_NOTE_API
from .staff.activity_log import API as ACTIVITY_LOG_API
from .staff.submitted_document import API as SUBMITTED_DOCUMENT_API

__all__ = ('API_BLUEPRINT', 'OPS_BLUEPRINT', 'STAFF_API_BLUEPRINT')

Expand Down Expand Up @@ -88,3 +89,4 @@
STAFF_API.add_namespace(STAFF_SUBMISSION_ITEM_NOTE_API)
STAFF_API.add_namespace(STAFF_USER_API)
STAFF_API.add_namespace(ACTIVITY_LOG_API)
STAFF_API.add_namespace(SUBMITTED_DOCUMENT_API)
64 changes: 64 additions & 0 deletions submit-api/src/submit_api/resources/staff/submitted_document.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright © 2024 Province of British Columbia
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# 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.
"""API endpoints for managing a document resource."""

from http import HTTPStatus
from flask import request

from flask_restx import Namespace, Resource, cors

from submit_api.auth import auth
from submit_api.models.account_project_search_options import DocumentSearchOptions
from submit_api.resources.apihelper import Api as ApiHelper
from submit_api.schemas.submission import SubmittedDocumentByProjectSchema
from submit_api.services.submitted_document_service import DocumentService
from submit_api.utils.roles import EpicSubmitRole
from submit_api.utils.util import cors_preflight


API = Namespace("documents", description="Endpoints for Submitted Document Management")
"""Custom exception messages
"""

document_list_model = ApiHelper.convert_ma_schema_to_restx_model(
API, SubmittedDocumentByProjectSchema(), "Document"
)


@cors_preflight("GET, OPTIONS")
@API.route(
"",
methods=["GET", "OPTIONS"],
)
class AccountDocuments(Resource):
"""Resource for managing submitted documents."""

@staticmethod
@ApiHelper.swagger_decorators(API, endpoint_description="Get submitted documents")
@API.response(
code=HTTPStatus.OK, model=document_list_model, description="Get documents"
)
@API.response(HTTPStatus.BAD_REQUEST, "Bad Request")
@auth.has_one_of_roles([EpicSubmitRole.EAO_VIEW.value])
@cors.crossdomain(origin="*")
def get():
"""Get all submitted documents."""
args = request.args
search_text = args.get('search_text')
search_options = DocumentSearchOptions(
search_text=search_text,
)

documents = DocumentService.get_all_documents(search_options)
return SubmittedDocumentByProjectSchema(many=True).dump(documents), HTTPStatus.OK
17 changes: 17 additions & 0 deletions submit-api/src/submit_api/schemas/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from marshmallow import EXCLUDE, Schema, fields, pre_dump

from submit_api.enums.item_status import ItemStatus
from submit_api.models.submission import SubmissionStatus


Expand Down Expand Up @@ -78,3 +79,19 @@ class Meta: # pylint: disable=too-few-public-methods
data = fields.Dict(data_key="data")
item_id = fields.Int(data_key="item_id")
created_by = fields.Str(data_key="created_by", required=False)

class SubmittedDocumentByProjectSchema(Schema):
"""Submitted document schema."""

class Meta: # pylint: disable=too-few-public-methods
"""Exclude unknown fields in the deserialized output."""

unknown = EXCLUDE

id = fields.Int(data_key="id")
name = fields.Str(data_key="name")
url = fields.Str(data_key="url")
project_name = fields.Str(data_key="project_name")
status = fields.Enum(data_key="status", enum=ItemStatus)
submitted_on = fields.DateTime(data_key="submitted_on")
version = fields.Str(data_key="version")
13 changes: 13 additions & 0 deletions submit-api/src/submit_api/services/submitted_document_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Service for submitted document management."""

from submit_api.models.account_project_search_options import DocumentSearchOptions
from submit_api.models.queries.submitted_document import DocumentQueries


class DocumentService:
"""Submitted document management service."""

@classmethod
def get_all_documents(cls, search_options: DocumentSearchOptions = None):
"""Get all documents."""
return DocumentQueries.get_filtered_documents(search_options)
66 changes: 66 additions & 0 deletions submit-web/src/components/Documents/DocumentTableHead.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { TableHead, TableRow } from "@mui/material";
import { BCDesignTokens } from "epic.theme";
import { SubmitTableHeadCell } from "@/components/Shared/Table/common";

export default function DocumentTableHead() {
return (
<TableHead
sx={{
border: 0,
".MuiTableCell-root": {
p: BCDesignTokens.layoutPaddingXsmall,
},
}}
>
<TableRow>
<SubmitTableHeadCell
sx={{
width: "20%",
}}
>
Project
</SubmitTableHeadCell>
<SubmitTableHeadCell
align="left"
sx={{
width: "45%",
}}
>
Document Name
</SubmitTableHeadCell>
<SubmitTableHeadCell
align="left"
sx={{
width: "5%",
}}
>
Version
</SubmitTableHeadCell>
<SubmitTableHeadCell
align="left"
sx={{
width: "10%",
}}
>
Submission Date
</SubmitTableHeadCell>
<SubmitTableHeadCell
align="center"
sx={{
width: "15%",
}}
>
Status
</SubmitTableHeadCell>
<SubmitTableHeadCell
align="left"
sx={{
width: "5%",
}}
>
Actions
</SubmitTableHeadCell>
</TableRow>
</TableHead>
);
}
77 changes: 77 additions & 0 deletions submit-web/src/components/Documents/DocumentTableRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useState } from "react";
import dateUtils from "@/utils/dateUtils";
import { Link as MuiLink, Typography } from "@mui/material";
import { BCDesignTokens } from "epic.theme";
import { SubmittedDocument } from "@/models/Submission";
import { SubmissionStatusChip } from "../SubmissionStatusChip";
import { SubmitTableCell } from "@/components/Shared/Table/common";
import { TableRow } from "@mui/material";
import { getObjectFromS3 } from "@/components/Shared/Table/utils";
import { notify } from "@/components/Shared/Snackbar/snackbarStore";

interface DocumentRowProps {
submittedDocument: SubmittedDocument;
}

export default function DocumentTableRow({ submittedDocument }: DocumentRowProps) {
const { name, url } = submittedDocument;

const [pendingGetObject, setPendingGetObject] = useState(false)

const downloadDocument = async () => {
try {
if (pendingGetObject) return;
setPendingGetObject(true);
await getObjectFromS3({ name, url });
} catch (e) {
notify.error("Failed to download document");
} finally {
setPendingGetObject(false);
}
};

const openDocument = () => {
downloadDocument();
};

return (
<>
<TableRow>
<SubmitTableCell align="left">
{submittedDocument.project_name ?? ""}
</SubmitTableCell>
<SubmitTableCell>
<Typography
variant="body1"
color="inherit"
sx={{
overflow: "clip",
textOverflow: "ellipsis",
cursor: "pointer",
mx: 0.5,
}}
>
<MuiLink onClick={openDocument}>{submittedDocument.name}</MuiLink>
</Typography>
</SubmitTableCell>
<SubmitTableCell align="right">
{submittedDocument.version ?? ""}
</SubmitTableCell>
<SubmitTableCell align="center">
{dateUtils.formatDate(submittedDocument.submitted_on)}
</SubmitTableCell>
<SubmitTableCell
align="right"
sx={{
pr: BCDesignTokens.layoutPaddingSmall,
}}
>
<SubmissionStatusChip status={submittedDocument.status ?? ""} />
</SubmitTableCell>
<SubmitTableCell align="center">
{''}
</SubmitTableCell>
</TableRow>
</>
);
}
Loading

0 comments on commit 510b076

Please sign in to comment.