Skip to content

Commit

Permalink
Add tasks from project page (#109)
Browse files Browse the repository at this point in the history
  • Loading branch information
uittenbroekrobbert authored Jul 25, 2024
2 parents 5ea710b + d582dfe commit 7304687
Show file tree
Hide file tree
Showing 23 changed files with 2,159 additions and 36 deletions.
2 changes: 1 addition & 1 deletion amt/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class AMTValidationException(ValidationException):

class AMTError(RuntimeError):
"""
A generic, Tad-specific error.
A generic, AMT-specific error.
"""


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""add reference to project in task
Revision ID: 2c84c4ad5b68
Revises: c21dd0bc2c85
Create Date: 2024-07-22 15:28:45.628332
"""

from collections.abc import Sequence

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision: str = "2c84c4ad5b68"
down_revision: str | None = "c21dd0bc2c85"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("task", schema=None) as batch_op:
batch_op.add_column(sa.Column("project_id", sa.Integer(), nullable=True))
batch_op.create_foreign_key("fk_project_id", "project", ["project_id"], ["id"])

# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("task", schema=None) as batch_op:
batch_op.drop_constraint("fk_project_id", type_="foreignkey")
batch_op.drop_column("project_id")

# ### end Alembic commands ###
4 changes: 4 additions & 0 deletions amt/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ class Task(SQLModel, table=True):
sort_order: float
status_id: int | None = SQLField(default=None, foreign_key="status.id")
user_id: int | None = SQLField(default=None, foreign_key="user.id")
# TODO: (Christopher) SQLModel does not allow to give the below restraint an name
# which is needed for alembic. This results in changing the migration file
# manually to give the restrain a name.
project_id: int | None = SQLField(default=None, foreign_key="project.id")
# todo(robbert) Tasks probably are grouped (and sub-grouped), so we probably need a reference to a group_id
12 changes: 12 additions & 0 deletions amt/repositories/statuses.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,15 @@ def find_by_id(self, status_id: int) -> Status:
return self.session.exec(statement).one()
except NoResultFound as e:
raise RepositoryError from e

def find_by_name(self, status_name: str) -> Status:
"""
Returns the status with the given name or an exception if the name does not exist.
:param status_name: the name of the status
:return: the status with the given name or an exception
"""
try:
statement = select(Status).where(Status.name == status_name)
return self.session.exec(statement).one()
except NoResultFound as e:
raise RepositoryError from e
29 changes: 28 additions & 1 deletion amt/repositories/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from fastapi import Depends
from sqlalchemy.exc import NoResultFound
from sqlmodel import Session, select
from sqlmodel import Session, and_, select

from amt.core.exceptions import RepositoryError
from amt.models import Task
Expand Down Expand Up @@ -38,6 +38,20 @@ def find_by_status_id(self, status_id: int) -> Sequence[Task]:
statement = select(Task).where(Task.status_id == status_id).order_by(Task.sort_order) # pyright: ignore [reportUnknownMemberType, reportCallIssue, reportUnknownVariableType, reportArgumentType]
return self.session.exec(statement).all()

def find_by_project_id_and_status_id(self, project_id: int, status_id: int) -> Sequence[Task]:
"""
Returns all tasks in the repository for the given project_id.
:param project_id: the project_id to filter on
:return: a list of tasks in the repository for the given project_id
"""
# todo (Robbert): we 'type ignore' Task.sort_order because it works correctly, but pyright does not agree
statement = (
select(Task)
.where(and_(Task.status_id == status_id, Task.project_id == project_id))
.order_by(Task.sort_order) # pyright: ignore [reportUnknownMemberType, reportCallIssue, reportUnknownVariableType, reportArgumentType]
)
return self.session.exec(statement).all()

def save(self, task: Task) -> Task:
"""
Stores the given task in the repository or throws a RepositoryException
Expand All @@ -53,6 +67,19 @@ def save(self, task: Task) -> Task:
raise RepositoryError from e
return task

def save_all(self, tasks: Sequence[Task]) -> None:
"""
Stores the given tasks in the repository or throws a RepositoryException
:param tasks: the tasks to store
:return: the updated tasks after storing
"""
try:
self.session.add_all(tasks)
self.session.commit()
except Exception as e:
self.session.rollback()
raise RepositoryError from e

def delete(self, task: Task) -> None:
"""
Deletes the given task in the repository or throws a RepositoryException
Expand Down
1 change: 1 addition & 0 deletions amt/schema/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class Owner(BaseModel):
class InstrumentTask(BaseModel):
question: str
urn: str
type: list[str] = Field(default=[])
lifecycle: list[str]


Expand Down
11 changes: 7 additions & 4 deletions amt/services/instruments.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from collections.abc import Sequence

import yaml

Expand Down Expand Up @@ -30,14 +31,16 @@ def fetch_github_content(self, url: str) -> Instrument:

return Instrument(**data)

def fetch_instruments(self) -> list[Instrument]:
def fetch_instruments(self, urns: Sequence[str] | None = None) -> list[Instrument]:
content_list = self.fetch_github_content_list()

instruments: list[Instrument] = []

for content in content_list.root: # TODO(Berry): fix root field
instrument = self.fetch_github_content(str(content.download_url))
instruments.append(instrument)
if urns is None:
instruments.append(instrument)
else:
if instrument.urn in set(urns):
instruments.append(instrument)
return instruments

#
15 changes: 14 additions & 1 deletion amt/services/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,23 @@
from amt.schema.instrument import InstrumentBase
from amt.schema.project import ProjectNew
from amt.schema.system_card import SystemCard
from amt.services.instruments import InstrumentsService
from amt.services.storage import Storage, StorageFactory
from amt.services.tasks import TasksService

logger = logging.getLogger(__name__)


class ProjectsService:
def __init__(self, repository: Annotated[ProjectsRepository, Depends(ProjectsRepository)]) -> None:
def __init__(
self,
repository: Annotated[ProjectsRepository, Depends(ProjectsRepository)],
task_service: Annotated[TasksService, Depends(TasksService)],
instrument_service: Annotated[InstrumentsService, Depends(InstrumentsService)],
) -> None:
self.repository = repository
self.instrument_service = instrument_service
self.task_service = task_service

def get(self, project_id: int) -> Project | None:
project = None
Expand Down Expand Up @@ -48,6 +57,10 @@ def create(self, project_new: ProjectNew) -> Project:
)
storage_writer.write(system_card.model_dump())

selected_instruments = self.instrument_service.fetch_instruments(project_new.instruments) # type: ignore
for instrument in selected_instruments:
self.task_service.create_instrument_tasks(instrument.tasks, project)

return project

def update(self, project: Project) -> Project:
Expand Down
3 changes: 3 additions & 0 deletions amt/services/statuses.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ def __init__(self, repository: Annotated[StatusesRepository, Depends(StatusesRep
def get_status(self, status_id: int) -> Status:
return self.repository.find_by_id(status_id)

def get_status_by_name(self, status_name: str) -> Status:
return self.repository.find_by_name(status_name)

def get_statuses(self) -> Sequence[Status]:
return self.repository.find_all()
18 changes: 18 additions & 0 deletions amt/services/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

from fastapi import Depends

from amt.models.project import Project
from amt.models.task import Task
from amt.models.user import User
from amt.repositories.tasks import TasksRepository
from amt.schema.instrument import InstrumentTask
from amt.schema.system_card import SystemCard
from amt.services.statuses import StatusesService
from amt.services.storage import StorageFactory
Expand All @@ -28,10 +30,26 @@ def __init__(
def get_tasks(self, status_id: int) -> Sequence[Task]:
return self.repository.find_by_status_id(status_id)

def get_tasks_for_project(self, project_id: int, status_id: int) -> Sequence[Task]:
return self.repository.find_by_project_id_and_status_id(project_id, status_id)

def assign_task(self, task: Task, user: User) -> Task:
task.user_id = user.id
return self.repository.save(task)

def create_instrument_tasks(self, tasks: Sequence[InstrumentTask], project: Project) -> None:
# TODO: (Christopher) At this moment a status has to be retrieved from the DB. In the future
# we will have static statuses, so this will need to change.
status = self.statuses_service.get_status_by_name("todo")
self.repository.save_all(
[
# TODO: (Christopher) The ticket does not specify what to do when question type is not an
# open questions, hence for now all titles will be set to task.question.
Task(title=task.question, description="", project_id=project.id, status_id=status.id, sort_order=idx)
for idx, task in enumerate(tasks)
]
)

def move_task(
self, task_id: int, status_id: int, previous_sibling_id: int | None = None, next_sibling_id: int | None = None
) -> Task:
Expand Down
2 changes: 1 addition & 1 deletion amt/site/templates/index.html.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
</div>
<div class="section group">
{% for status in statuses_service.get_statuses() %}
{{ render.column(status, translations, tasks_service) }}
{{ render.column(project, status, translations, tasks_service) }}
{% endfor %}
</div>
</div>
Expand Down
10 changes: 8 additions & 2 deletions amt/site/templates/macros/render.html.j2
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
{% macro column(status, translations, tasks_service) -%}
{% macro column(project, status, translations, tasks_service) -%}
<div class="col span_1_of_4">
<h3 class="text-center-horizontal margin-bottom--small">{{ translations[status.name] }}</h3>
<div class="progress_cards_container sortable" data-id="{{ status.id }}" id="column-{{ status.id }}">
{% if project and project.id %}
{% for task in tasks_service.get_tasks_for_project(project.id, status.id) %}
{{ render_task_card_full(task) }}
{% endfor %}
{% else %}
{% for task in tasks_service.get_tasks(status.id) %}
{{ render_task_card_full(task) }}
{% endfor %}
{% endif %}
</div>
</div>
{% endmacro -%}
Expand All @@ -18,7 +24,7 @@

{% macro render_task_card_content(task) -%}
<div id="card-content-{{ task.id }}" data-id="{{ task.id }}">
<h4 class="margin-bottom--extra-small">{{ task.title }}</h4>
<h4 class="margin-bottom--extra-small">{{ task.title | truncate(100)}}</h4>
<div>{{ task.description }}</div>
{% if task.user_id %}
<div class="progress_card_assignees_container">
Expand Down
2 changes: 1 addition & 1 deletion amt/site/templates/pages/index.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
</div>
<div class="section group">
{% for status in statuses_service.get_statuses() %}
{{ render.column(status, translations, tasks_service) }}
{{ render.column(project, status, translations, tasks_service) }}
{% endfor %}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion check-state
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/usr/bin/env bash

python -m tad "$@"
python -m amt "$@"
16 changes: 8 additions & 8 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 7304687

Please sign in to comment.