Skip to content

Commit

Permalink
Merge pull request #1269 from ScilifelabDataCentre/dev
Browse files Browse the repository at this point in the history
New release - busy status
  • Loading branch information
i-oden authored Sep 13, 2022
2 parents 6aa7579 + 8c2bcab commit cd80b57
Show file tree
Hide file tree
Showing 10 changed files with 453 additions and 68 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,5 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe
## Sprint (2022-09-02 - 2022-09-16)

- Add storage usage information in the Units listing table for Super Admin ([#1264](https://github.com/ScilifelabDataCentre/dds_web/pull/1264))
- New endpoint for setting project as busy / not busy ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266))
- Check for if project busy before status change ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266))
1 change: 1 addition & 0 deletions dds_web/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def output_json(data, code, headers=None):
api.add_resource(project.ProjectUsers, "/proj/users", endpoint="list_project_users")
api.add_resource(project.ProjectStatus, "/proj/status", endpoint="project_status")
api.add_resource(project.ProjectAccess, "/proj/access", endpoint="project_access")
api.add_resource(project.ProjectBusy, "/proj/busy", endpoint="project_busy")

# User management ################################################################ User management #
api.add_resource(user.RetrieveUserInfo, "/user/info", endpoint="user_info")
Expand Down
202 changes: 136 additions & 66 deletions dds_web/api/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
DeletionError,
BucketNotFoundError,
KeyNotFoundError,
NoSuchProjectError,
ProjectBusyError,
S3ConnectionError,
NoSuchUserError,
)
Expand Down Expand Up @@ -86,82 +88,109 @@ def post(self):
project = dds_web.utils.collect_project(project_id=project_id)
dds_web.utils.verify_project_access(project=project)

# Check if valid status
json_input = flask.request.json
new_status = json_input.get("new_status")
if not new_status:
raise DDSArgumentError(message="No status transition provided. Specify the new status.")

# Override default to send email
send_email = json_input.get("send_email", True)

# Initial variable definition
curr_date = dds_web.utils.current_time()
delete_message = ""
is_aborted = False

# Moving to Available
if new_status == "Available":
deadline_in = json_input.get("deadline", project.responsible_unit.days_in_available)
new_status_row = self.release_project(
project=project, current_time=curr_date, deadline_in=deadline_in
)
elif new_status == "In Progress":
new_status_row = self.retract_project(project=project, current_time=curr_date)
elif new_status == "Expired":
deadline_in = json_input.get("deadline", project.responsible_unit.days_in_expired)
new_status_row = self.expire_project(
project=project, current_time=curr_date, deadline_in=deadline_in
)
elif new_status == "Deleted":
new_status_row, delete_message = self.delete_project(
project=project, current_time=curr_date
)
elif new_status == "Archived":
is_aborted = json_input.get("is_aborted", False)
new_status_row, delete_message = self.archive_project(
project=project, current_time=curr_date, aborted=is_aborted
# Cannot change project status if project is busy
if project.busy:
raise ProjectBusyError(
message=(
f"The project '{project_id}' is currently busy with upload/download/deletion. "
"Please try again later. \n\nIf you know the project is not busy, contact support."
)
)
else:
raise DDSArgumentError(message="Invalid status")
self.set_busy(project=project, busy=True)

try:
project.project_statuses.append(new_status_row)
db.session.commit()
except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.SQLAlchemyError) as err:
flask.current_app.logger.exception(err)
db.session.rollback()
raise DatabaseError(
message=str(err),
alt_message=(
"Status was not updated"
+ (
": Database malfunction."
if isinstance(err, sqlalchemy.exc.OperationalError)
else ": Server Error."
)
),
) from err
# Check if valid status
json_input = flask.request.json
new_status = json_input.get("new_status")
if not new_status:
raise DDSArgumentError(
message="No status transition provided. Specify the new status."
)

# Override default to send email
send_email = json_input.get("send_email", True)

# Mail users once project is made available
if new_status == "Available" and send_email:
for user in project.researchusers:
AddUser.compose_and_send_email_to_user(
userobj=user.researchuser, mail_type="project_release", project=project
# Initial variable definition
curr_date = dds_web.utils.current_time()
delete_message = ""
is_aborted = False

# Moving to Available
if new_status == "Available":
deadline_in = json_input.get("deadline", project.responsible_unit.days_in_available)
new_status_row = self.release_project(
project=project, current_time=curr_date, deadline_in=deadline_in
)
elif new_status == "In Progress":
new_status_row = self.retract_project(project=project, current_time=curr_date)
elif new_status == "Expired":
deadline_in = json_input.get("deadline", project.responsible_unit.days_in_expired)
new_status_row = self.expire_project(
project=project, current_time=curr_date, deadline_in=deadline_in
)
elif new_status == "Deleted":
new_status_row, delete_message = self.delete_project(
project=project, current_time=curr_date
)
elif new_status == "Archived":
is_aborted = json_input.get("is_aborted", False)
new_status_row, delete_message = self.archive_project(
project=project, current_time=curr_date, aborted=is_aborted
)
else:
raise DDSArgumentError(message="Invalid status")

return_message = f"{project.public_id} updated to status {new_status}" + (
" (aborted)" if new_status == "Archived" and is_aborted else ""
)
try:
project.project_statuses.append(new_status_row)
project.busy = False
db.session.commit()
except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.SQLAlchemyError) as err:
flask.current_app.logger.exception(err)
db.session.rollback()
raise DatabaseError(
message=str(err),
alt_message=(
"Status was not updated"
+ (
": Database malfunction."
if isinstance(err, sqlalchemy.exc.OperationalError)
else ": Server Error."
)
),
) from err

if new_status != "Available":
return_message += delete_message + "."
else:
return_message += (
f". An e-mail notification has{' not ' if not send_email else ' '}been sent."
# Mail users once project is made available
if new_status == "Available" and send_email:
for user in project.researchusers:
AddUser.compose_and_send_email_to_user(
userobj=user.researchuser, mail_type="project_release", project=project
)

return_message = f"{project.public_id} updated to status {new_status}" + (
" (aborted)" if new_status == "Archived" and is_aborted else ""
)

if new_status != "Available":
return_message += delete_message + "."
else:
return_message += (
f". An e-mail notification has{' not ' if not send_email else ' '}been sent."
)
except:
self.set_busy(project=project, busy=False)
raise

return {"message": return_message}

@staticmethod
@dbsession
def set_busy(project: models.Project, busy: bool) -> None:
"""Set project as not busy."""
flask.current_app.logger.info(
f"Setting busy status. Project: '{project.public_id}', Busy: {busy}"
)
project.busy = busy

def check_transition_possible(self, current_status, new_status):
"""Check if the transition is valid."""
valid_statuses = {
Expand Down Expand Up @@ -867,3 +896,44 @@ def give_project_access(project_list, current_user, user):
] = "You do not have access to this project. Please contact the responsible unit."

return fix_errors


class ProjectBusy(flask_restful.Resource):
@auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"])
@logging_bind_request
@dbsession
@json_required
def put(self):
"""Set project to busy / not busy."""
# Get project ID, project and verify access
project_id = dds_web.utils.get_required_item(obj=flask.request.args, req="project")
project = dds_web.utils.collect_project(project_id=project_id)
dds_web.utils.verify_project_access(project=project)

# Get busy or not busy
set_to_busy = flask.request.json.get("busy")
if set_to_busy is None:
raise DDSArgumentError(message="Missing information about setting busy or not busy.")

if set_to_busy:
# Check if project is busy
if project.busy:
return {"ok": False, "message": "The project is already busy, cannot proceed."}

# Set project as busy
project.busy = True
else:
# Check if project is not busy
if not project.busy:
return {
"ok": False,
"message": "The project is already not busy, cannot proceed.",
}

# Set project to not busy
project.busy = False

return {
"ok": True,
"message": f"Project {project_id} was set to {'busy' if set_to_busy else 'not busy'}.",
}
1 change: 1 addition & 0 deletions dds_web/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ class Project(db.Model):
released = db.Column(db.DateTime(), nullable=True)
is_active = db.Column(db.Boolean, unique=False, nullable=False, default=True, index=True)
done = db.Column(db.Boolean, unique=False, nullable=False, default=False)
busy = db.Column(db.Boolean, unique=False, nullable=False, default=False)

# Foreign keys & relationships
unit_id = db.Column(db.Integer, db.ForeignKey("units.id", ondelete="RESTRICT"), nullable=True)
Expand Down
11 changes: 11 additions & 0 deletions dds_web/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,17 @@ def __init__(self, message="Project ID missing!"):
general_logger.warning(message)


class ProjectBusyError(LoggedHTTPException):
"""Something has gone wrong when transitioning between busy and not busy project."""

code = http.HTTPStatus.BAD_REQUEST

def __init__(self, message):
super().__init__(message)

general_logger.warning(message)


class DDSArgumentError(LoggedHTTPException):
"""Base class for errors occurring due to missing request arguments."""

Expand Down
2 changes: 1 addition & 1 deletion dds_web/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.1.1"
__version__ = "1.1.2"
28 changes: 28 additions & 0 deletions migrations/versions/6e9b74878553_add_busy_to_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""add-busy-to-project
Revision ID: 6e9b74878553
Revises: 02acefb3057a
Create Date: 2022-09-07 09:09:06.081829
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

# revision identifiers, used by Alembic.
revision = "6e9b74878553"
down_revision = "02acefb3057a"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("projects", sa.Column("busy", sa.Boolean(), nullable=False))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("projects", "busy")
# ### end Alembic commands ###
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ class DDSEndpoint:
PROJECT_CREATE = BASE_ENDPOINT + "/proj/create"
PROJECT_STATUS = BASE_ENDPOINT + "/proj/status"
PROJECT_ACCESS = BASE_ENDPOINT + "/proj/access"
PROJECT_BUSY = BASE_ENDPOINT + "/proj/busy"

# Listing urls
LIST_PROJ = BASE_ENDPOINT + "/proj/list"
Expand Down
Loading

0 comments on commit cd80b57

Please sign in to comment.