Skip to content

Commit

Permalink
feat: mark queries as started from the UI (#1939) (#1941)
Browse files Browse the repository at this point in the history
  • Loading branch information
holtgrewe authored Aug 30, 2024
1 parent 60c03da commit 1a1a7ff
Show file tree
Hide file tree
Showing 14 changed files with 740 additions and 418 deletions.
2 changes: 1 addition & 1 deletion backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ attrs = "~=24.2"
beautifulsoup4 = "~=4.9"
cattrs = "*"
django = "*" # whatever django-sodar-core enforces
django-clone = "~=5.3"
django-clone = "*"
django-cryptographic-fields-bihealth = "~=0.6.0"
django-debug-toolbar = "*"
django-extensions = "~=3.2"
Expand Down
568 changes: 292 additions & 276 deletions backend/Pipfile.lock

Large diffs are not rendered by default.

76 changes: 65 additions & 11 deletions backend/seqvars/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from django.contrib.auth import get_user_model
from django.db import models, transaction
from django_pydantic_field.v2.fields import PydanticSchemaField as SchemaField
import model_clone
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
import pydantic
Expand Down Expand Up @@ -471,6 +472,9 @@ class Meta:
class BaseModel(models.Model):
"""Base model with sodar_uuid and creation/update time."""

#: Let the ``sodar_uuid`` value be re-created when cloning.
_clone_excluded_fields = ["sodar_uuid"]

#: UUID used in URLs.
sodar_uuid = models.UUIDField(default=uuid_object.uuid4, unique=True)
#: DateTime of creation
Expand Down Expand Up @@ -881,9 +885,23 @@ def from_predefinedquery(
return querysettings


class SeqvarsQuerySettings(BaseModel):
class SeqvarsQuerySettings(model_clone.CloneMixin, BaseModel):
"""The query settings for a case."""

#: Let the ``sodar_uuid`` value be re-created when cloning.
_clone_excluded_fields = ["sodar_uuid"]
#: Explicitely set the "owned" one-to-one relations to clone.
_clone_o2o_fields = [
"genotype",
"variantprio",
"frequency",
"consequence",
"locus",
"phenotypeprio",
"quality",
"clinvar",
]

#: Custom manager with ``from_predefinedquery()``.
objects = SeqvarsQuerySettingsManager()

Expand Down Expand Up @@ -1241,9 +1259,12 @@ def from_presets(
)


class SeqvarsQuerySettingsGenotype(SeqvarsQuerySettingsCategoryBase):
class SeqvarsQuerySettingsGenotype(model_clone.CloneMixin, SeqvarsQuerySettingsCategoryBase):
"""Query settings for per-sample genotype filtration."""

#: Let the ``sodar_uuid`` value be re-created when cloning.
_clone_excluded_fields = ["sodar_uuid"]

#: Custom manager with ``from_presets()``.
objects = SeqvarsQuerySettingsGenotypeManager()

Expand Down Expand Up @@ -1332,9 +1353,12 @@ def from_presets(
)


class SeqvarsQuerySettingsQuality(SeqvarsQuerySettingsCategoryBase):
class SeqvarsQuerySettingsQuality(model_clone.CloneMixin, SeqvarsQuerySettingsCategoryBase):
"""Query settings for per-sample quality filtration."""

#: Let the ``sodar_uuid`` value be re-created when cloning.
_clone_excluded_fields = ["sodar_uuid"]

#: Custom manager with ``from_presets()``.
objects = SeqvarsQuerySettingsQualityManager()

Expand Down Expand Up @@ -1371,10 +1395,13 @@ def from_presets(


class SeqvarsQuerySettingsConsequence(
SeqvarsConsequenceSettingsBase, SeqvarsQuerySettingsCategoryBase
model_clone.CloneMixin, SeqvarsConsequenceSettingsBase, SeqvarsQuerySettingsCategoryBase
):
"""Presets for consequence-related settings within a ``QuerySettingsSet``."""

#: Let the ``sodar_uuid`` value be re-created when cloning.
_clone_excluded_fields = ["sodar_uuid"]

#: Custom manager with ``from_presets()``.
objects = SeqvarsQuerySettingsConsequenceManager()

Expand Down Expand Up @@ -1404,9 +1431,14 @@ def from_presets(
)


class SeqvarsQuerySettingsLocus(SeqvarsLocusSettingsBase, SeqvarsQuerySettingsCategoryBase):
class SeqvarsQuerySettingsLocus(
model_clone.CloneMixin, SeqvarsLocusSettingsBase, SeqvarsQuerySettingsCategoryBase
):
"""Presets for locus-related settings within a ``QuerySettingsSet``."""

#: Let the ``sodar_uuid`` value be re-created when cloning.
_clone_excluded_fields = ["sodar_uuid"]

#: Custom manager with ``from_presets()``.
objects = SeqvarsQuerySettingsLocusManager()

Expand Down Expand Up @@ -1435,9 +1467,14 @@ def from_presets(
)


class SeqvarsQuerySettingsFrequency(SeqvarsFrequencySettingsBase, SeqvarsQuerySettingsCategoryBase):
class SeqvarsQuerySettingsFrequency(
model_clone.CloneMixin, SeqvarsFrequencySettingsBase, SeqvarsQuerySettingsCategoryBase
):
"""Query settings in the frequency category."""

#: Let the ``sodar_uuid`` value be re-created when cloning.
_clone_excluded_fields = ["sodar_uuid"]

#: Custom manager with ``from_presets()``.
objects = SeqvarsQuerySettingsFrequencyManager()

Expand Down Expand Up @@ -1468,10 +1505,13 @@ def from_presets(


class SeqvarsQuerySettingsPhenotypePrio(
SeqvarsPhenotypePrioSettingsBase, SeqvarsQuerySettingsCategoryBase
model_clone.CloneMixin, SeqvarsPhenotypePrioSettingsBase, SeqvarsQuerySettingsCategoryBase
):
"""Presets for phenotype priorization--related settings within a ``QueryPresetsSetVersion``."""

#: Let the ``sodar_uuid`` value be re-created when cloning.
_clone_excluded_fields = ["sodar_uuid"]

#: Custom manager with ``from_presets()``.
objects = SeqvarsQuerySettingsPhenotypePrioManager()

Expand Down Expand Up @@ -1500,10 +1540,13 @@ def from_presets(


class SeqvarsQuerySettingsVariantPrio(
SeqvarsVariantPrioSettingsBase, SeqvarsQuerySettingsCategoryBase
model_clone.CloneMixin, SeqvarsVariantPrioSettingsBase, SeqvarsQuerySettingsCategoryBase
):
"""Query settings in the variant priorization category."""

#: Let the ``sodar_uuid`` value be re-created when cloning.
_clone_excluded_fields = ["sodar_uuid"]

#: Custom manager with ``from_presets()``.
objects = SeqvarsQuerySettingsVariantPrioManager()

Expand Down Expand Up @@ -1535,9 +1578,14 @@ def from_presets(
)


class SeqvarsQuerySettingsClinvar(SeqvarsClinvarSettingsBase, SeqvarsQuerySettingsCategoryBase):
class SeqvarsQuerySettingsClinvar(
model_clone.CloneMixin, SeqvarsClinvarSettingsBase, SeqvarsQuerySettingsCategoryBase
):
"""Query settings in the variant priorization category."""

#: Let the ``sodar_uuid`` value be re-created when cloning.
_clone_excluded_fields = ["sodar_uuid"]

#: Custom manager with ``from_presets()``.
objects = SeqvarsQuerySettingsClinvarManager()

Expand Down Expand Up @@ -1566,14 +1614,17 @@ def from_predefinedquery(
return super().create(column_settings=[])


class SeqvarsQueryColumnsConfig(SeqvarsColumnsSettingsBase, BaseModel):
class SeqvarsQueryColumnsConfig(model_clone.CloneMixin, SeqvarsColumnsSettingsBase, BaseModel):
"""Per-query (not execution) configuration of columns.
This will be copied over from the presets to the query and not the query
settings. Thus, it will not be persisted by query execution but is
editable after query execution.
"""

#: Let the ``sodar_uuid`` value be re-created when cloning.
_clone_excluded_fields = ["sodar_uuid"]

#: Custom manager with ``from_predefinedquery()``.
objects = SeqvarsQueryColumnsConfigManager()

Expand Down Expand Up @@ -1619,9 +1670,12 @@ def _pick_query_rank(self, session: CaseAnalysisSession) -> int:
return rank


class SeqvarsQuery(BaseModel):
class SeqvarsQuery(model_clone.CloneMixin, BaseModel):
"""Allows users to prepare seqvar queries for execution and execute them."""

#: Let the ``sodar_uuid`` value be re-created when cloning.
_clone_excluded_fields = ["sodar_uuid"]

#: Override the manager so we can easily create from predefined queries.
objects = SeqvarsQueryManager()

Expand Down
29 changes: 29 additions & 0 deletions backend/seqvars/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,35 @@ def test_from_predefinedquery(self):
self.assertEqual(SeqvarsQuerySettingsQuality.objects.count(), 1)
self.assertEqual(SeqvarsQuerySettingsClinvar.objects.count(), 1)

def test_make_clone(self):
predefinedquery = SeqvarsPredefinedQueryFactory()
session = CaseAnalysisSessionFactory()
querysettings = SeqvarsQuerySettings.objects.from_predefinedquery(
session=session,
predefinedquery=predefinedquery,
)
self.assertEqual(SeqvarsQuerySettings.objects.count(), 1)
self.assertEqual(SeqvarsQuerySettingsGenotype.objects.count(), 1)
self.assertEqual(SeqvarsQuerySettingsVariantPrio.objects.count(), 1)
self.assertEqual(SeqvarsQuerySettingsFrequency.objects.count(), 1)
self.assertEqual(SeqvarsQuerySettingsConsequence.objects.count(), 1)
self.assertEqual(SeqvarsQuerySettingsLocus.objects.count(), 1)
self.assertEqual(SeqvarsQuerySettingsPhenotypePrio.objects.count(), 1)
self.assertEqual(SeqvarsQuerySettingsQuality.objects.count(), 1)
self.assertEqual(SeqvarsQuerySettingsClinvar.objects.count(), 1)

cloned_querysettings = querysettings.make_clone()
_ = cloned_querysettings
self.assertEqual(SeqvarsQuerySettings.objects.count(), 2)
self.assertEqual(SeqvarsQuerySettingsGenotype.objects.count(), 2)
self.assertEqual(SeqvarsQuerySettingsVariantPrio.objects.count(), 2)
self.assertEqual(SeqvarsQuerySettingsFrequency.objects.count(), 2)
self.assertEqual(SeqvarsQuerySettingsConsequence.objects.count(), 2)
self.assertEqual(SeqvarsQuerySettingsLocus.objects.count(), 2)
self.assertEqual(SeqvarsQuerySettingsPhenotypePrio.objects.count(), 2)
self.assertEqual(SeqvarsQuerySettingsQuality.objects.count(), 2)
self.assertEqual(SeqvarsQuerySettingsClinvar.objects.count(), 2)


class TestSeqvarsQuerySettingsGenotype(TestCaseSnapshot, TestCase):

Expand Down
30 changes: 30 additions & 0 deletions backend/seqvars/tests/test_views_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from seqvars.models import (
SeqvarsPredefinedQuery,
SeqvarsQuery,
SeqvarsQueryExecution,
SeqvarsQueryPresetsClinvar,
SeqvarsQueryPresetsColumns,
SeqvarsQueryPresetsConsequence,
Expand Down Expand Up @@ -2136,6 +2137,35 @@ def test_retrieve_nonexisting(self, kwargs_override: dict[str, Any]):
)
self.assertEqual(response.status_code, 404)

def test_start(self):
self.assertEqual(SeqvarsQueryExecution.objects.count(), 1)
self.assertEqual(SeqvarsQuery.objects.count(), 1)

with self.login(self.superuser):
response = self.client.post(
reverse(
"seqvars:api-queryexecution-start",
kwargs={
"query": self.query.sodar_uuid,
},
),
)
self.assertEqual(response.status_code, 200)

self.assertEqual(SeqvarsQuery.objects.count(), 1)
self.assertEqual(SeqvarsQueryExecution.objects.count(), 2)
self.assertEqual(
self.query.seqvarsqueryexecution_set.count(),
2,
)
new_seqvarqueryexecution = SeqvarsQueryExecution.objects.exclude(
pk=self.queryexecution.pk
).first()
self.assertEqual(
new_seqvarqueryexecution.state,
SeqvarsQueryExecution.STATE_QUEUED,
)


@freeze_time("2012-01-14 12:00:01")
class TestResultSetViewSet(ApiViewTestBase):
Expand Down
28 changes: 20 additions & 8 deletions backend/seqvars/views_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from modelcluster.queryset import FakeQuerySet
from projectroles.models import Project
from projectroles.views_api import SODARAPIProjectPermission
from rest_framework import viewsets
from rest_framework import serializers, viewsets
from rest_framework.decorators import action
from rest_framework.pagination import CursorPagination
from rest_framework.response import Response
Expand Down Expand Up @@ -447,13 +447,11 @@ def get_serializer_context(self):
class SeqvarsQueryViewSet(BaseViewSet):
"""Allow CRUD of the user's queries."""

# TODO XXX XXX ADD LAUNCH ACTION XXX XXX TODO

#: Define lookup URL kwarg.
lookup_url_kwarg = "query"
#: The default serializer class to use.
serializer_class = SeqvarsQuerySerializer
#: Override ``create`` and ``*-detail`` serializer to render all presets.
#: Override non-list serializers to serialize all preses.
action_serializers = {
"create": SeqvarsQueryDetailsSerializer,
"retrieve": SeqvarsQueryDetailsSerializer,
Expand All @@ -463,10 +461,6 @@ class SeqvarsQueryViewSet(BaseViewSet):
"create_from": SeqvarsQueryDetailsSerializer,
}

# def partial_update(self, *args, **kwargs):
# import pdb; pdb.set_trace()
# return super().partial_update(*args, **kwargs)

@extend_schema(request=SeqvarsQueryCreateFromSerializer)
@action(methods=["post"], detail=False)
@transaction.atomic()
Expand Down Expand Up @@ -536,8 +530,26 @@ class SeqvarsQueryExecutionViewSet(BaseReadOnlyViewSet):
#: Override ``retrieve`` serializer to render all presets.
action_serializers = {
"retrieve": SeqvarsQueryExecutionDetailsSerializer,
"start": SeqvarsQueryExecutionDetailsSerializer,
}

@extend_schema(request=serializers.Serializer)
@action(methods=["post"], detail=False)
@transaction.atomic()
def start(self, *args, **kwargs):
"""Create a new query execution for the given query."""
query = None
# TODO: check permissions on the source's project
query = SeqvarsQuery.objects.get(sodar_uuid=self.kwargs["query"])

instance = SeqvarsQueryExecution.objects.create(
state=SeqvarsQueryExecution.STATE_QUEUED,
query=query,
querysettings=query.settings.make_clone(),
)
serializer = self.get_serializer(instance)
return Response(serializer.data)

def get_queryset(self):
"""Return queryset with all ``QueryExecution`` records for the given query."""
result = SeqvarsQueryExecution.objects.all()
Expand Down
24 changes: 24 additions & 0 deletions backend/varfish/tests/drf_openapi_schema/varfish_api_schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2764,6 +2764,30 @@ paths:
schema:
$ref: '#/components/schemas/SeqvarsQueryExecutionDetails'
description: ''
/seqvars/api/queryexecution/{query}/start/:
post:
operationId: seqvars_api_queryexecution_start_create
description: Create a new query execution for the given query.
parameters:
- in: path
name: query
schema:
type: string
pattern: ^[0-9a-f-]+$
required: true
tags:
- seqvars
security:
- basicAuth: []
- cookieAuth: []
- knoxApiToken: []
responses:
'200':
content:
application/vnd.bihealth.varfish+json:
schema:
$ref: '#/components/schemas/SeqvarsQueryExecutionDetails'
description: ''
/seqvars/api/querypresetsclinvar/{querypresetssetversion}/:
get:
operationId: seqvars_api_querypresetsclinvar_list
Expand Down
28 changes: 26 additions & 2 deletions frontend/ext/varfish-api/src/lib/@tanstack/vue-query.gen.ts

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion frontend/ext/varfish-api/src/lib/services.gen.ts

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions frontend/ext/varfish-api/src/lib/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4160,6 +4160,16 @@ export type SeqvarsApiQueryexecutionRetrieveResponse = (SeqvarsQueryExecutionDet

export type SeqvarsApiQueryexecutionRetrieveError = unknown;

export type SeqvarsApiQueryexecutionStartCreateData = {
path: {
query: string;
};
};

export type SeqvarsApiQueryexecutionStartCreateResponse = (SeqvarsQueryExecutionDetails);

export type SeqvarsApiQueryexecutionStartCreateError = unknown;

export type SeqvarsApiQuerypresetsclinvarListData = {
path: {
querypresetssetversion: string;
Expand Down
Loading

0 comments on commit 1a1a7ff

Please sign in to comment.