diff --git a/common_lib/graphql/__init__.py b/common_lib/graphql/__init__.py index b301d078b31feefbfcc43fc9d80578c40f475cb6..5e2cd30e6434a3d61d2cf738f7e1d88862eb3c93 100644 --- a/common_lib/graphql/__init__.py +++ b/common_lib/graphql/__init__.py @@ -4,7 +4,6 @@ from .queries import ( ExerciseIdAction, EmailThreadsAction, TeamActionLogsAction, - TeamInjectSelectionAction, ExtendedTeamToolsAction, ExerciseConfigAction, UsersAction, diff --git a/common_lib/graphql/fragments.py b/common_lib/graphql/fragments.py index dbd5ded740eeec63b673cbc381f19b72d41aa616..878c3c22a9858d3b448886222044f3ea46513aa6 100644 --- a/common_lib/graphql/fragments.py +++ b/common_lib/graphql/fragments.py @@ -63,17 +63,6 @@ class Fragments: {}, ) - @staticmethod - def inject_option() -> ResponseField: - return ResponseField(["id", "name", "sender", "content"], {}) - - @staticmethod - def inject_selection() -> ResponseField: - return ResponseField( - ["id", "name", "optionEmail", "timestamp"], - {"team": Fragments.team(), "options": Fragments.inject_option()}, - ) - @staticmethod def email_template() -> ResponseField: return ResponseField( @@ -97,9 +86,9 @@ class Fragments: ) @staticmethod - def category() -> ResponseField: + def inject() -> ResponseField: return ResponseField( - ["id", "name", "auto", "email", "time", "delay", "organization"], {} + ["id", "name", "email", "time", "delay", "organization"], {} ) @staticmethod diff --git a/common_lib/graphql/queries.py b/common_lib/graphql/queries.py index b182bf44b845e01b62ce92786c9e04865706ee06..18a71397407231b94d0cb8b67458c6ee8ae7eebb 100644 --- a/common_lib/graphql/queries.py +++ b/common_lib/graphql/queries.py @@ -27,19 +27,6 @@ class ExerciseIdAction(APIAction): ) -class TeamInjectSelectionAction(APIAction): - query_name = "teamInjectSelections" - - def __init__(self, variables: Dict[str, Any]): - self.variables = variables - self.query = GraphQLQuery( - "query", - self.query_name, - [id_argument("team")], - Fragments.inject_selection(), - ) - - class EmailThreadsAction(APIAction): query_name = "emailThreads" diff --git a/common_lib/schema_types.py b/common_lib/schema_types.py index 1068392c062842b4b18f90c091f4f52779b40869..b84f967bc853d0be576d1cb52802809e49c0f88c 100644 --- a/common_lib/schema_types.py +++ b/common_lib/schema_types.py @@ -1,7 +1,7 @@ import graphene +from django.conf import settings from django.db import models from graphene_django import DjangoObjectType -from django.conf import settings from exercise.models import ( Exercise, @@ -33,8 +33,6 @@ from exercise_definition.models import ( ) from running_exercise.models import ( ActionLog, - InjectSelection, - InjectOption, EmailThread, Email, ThreadParticipant, @@ -229,16 +227,6 @@ class MilestoneStateType(DjangoObjectType): return [team.id for team in self.team_state.teams.all()] -class InjectSelectionType(DjangoObjectType): - class Meta: - model = InjectSelection - - -class InjectOptionType(DjangoObjectType): - class Meta: - model = InjectOption - - class EmailThreadType(DjangoObjectType): class Meta: model = EmailThread diff --git a/common_lib/subscription_handler.py b/common_lib/subscription_handler.py index 8daa21807bb0a1c3bc192c2fcea95ef5a753e9a6..7b40692acdaf53944681cda6a9a0bb04eda65e29 100644 --- a/common_lib/subscription_handler.py +++ b/common_lib/subscription_handler.py @@ -12,13 +12,11 @@ from exercise.subscription import ExercisesSubscription from running_exercise.models import ( ActionLog, EmailThread, - InjectSelection, ) from running_exercise.subscription import ( ExerciseLoopRunningSubscription, ActionLogsSubscription, MilestonesSubscription, - InjectSelectionsSubscription, EmailThreadSubscription, AnalyticsMilestonesSubscription, AnalyticsActionLogsSubscription, @@ -31,7 +29,6 @@ class SubscriptionHandler: _action_logs = ActionLogsSubscription _milestones = MilestonesSubscription _email_thread = EmailThreadSubscription - _inject_selection = InjectSelectionsSubscription _exercise_loop = ExerciseLoopRunningSubscription _exercises = ExercisesSubscription _analytics_milestones = AnalyticsMilestonesSubscription @@ -113,14 +110,6 @@ class SubscriptionHandler: group=str(participant.team_id), ) - @classmethod - def broadcast_inject_selection( - cls, inject_selection: InjectSelection, team: Team - ): - cls._inject_selection.broadcast( - payload=inject_selection, group=str(team.id) - ) - @classmethod def broadcast_exercise_loop(cls, exercise: Exercise, running: bool): cls._exercise_loop.broadcast(payload=running, group=str(exercise.id)) diff --git a/definitions/CHANGELOG.md b/definitions/CHANGELOG.md index fadf500b7d903334b822e5fe1da7e21e9de4b937..b596a1a633c9844809fcdf4178a6cb2dcd832587 100644 --- a/definitions/CHANGELOG.md +++ b/definitions/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.9.0 +Removed manual injects. #228 +All injects are now considered to be **automatic injects**. + +### injects.yml +- remove field `auto` + + ## 0.8.0 Add a new required concept called `learning objectives`. This concept _must_ be included in all exercise definition. #170 diff --git a/definitions/README.md b/definitions/README.md index 0c86039eb2922e06c6de97397fa944c07d604c56..116fb32b381ff45237e936f61241e5cc85795ca5 100644 --- a/definitions/README.md +++ b/definitions/README.md @@ -151,8 +151,6 @@ This file contains a list of injects. Each inject has the following fields: - **name**: _string, unique_ - name of the inject -- **auto**: _bool_ - when true, this inject will be automatically sent to a team once the conditions are fulfilled. - When false, it is first sent to the instructor for selection. - **time**: _int, default=0_ - time since the beginning of the exercise in minutes, the inject will not be processed until this time has been reached - **delay**: _int, default=0_ - time in minutes, whenever this inject is about to be sent, diff --git a/definitions/base_definition/config.yml b/definitions/base_definition/config.yml index b218b0ff7815a7cbd499bd3785f22ce1e31d42db..7f080d02430c271f258a8d81b52fefe4a9d2024e 100644 --- a/definitions/base_definition/config.yml +++ b/definitions/base_definition/config.yml @@ -1,3 +1,3 @@ exercise_duration: 60 show_exercise_time: False -version: 0.8.0 +version: 0.9.0 diff --git a/definitions/base_definition/injects.yml b/definitions/base_definition/injects.yml index 28c941ba6ea56d476e60606a6617bc0e58857ad2..58827345621cfe58b1d041b15461b82f2d8e38a9 100644 --- a/definitions/base_definition/injects.yml +++ b/definitions/base_definition/injects.yml @@ -1,5 +1,4 @@ - name: First Inject - auto: True organization: Org 1 alternatives: - name: First inject @@ -7,43 +6,8 @@ content: { content: first inject which activates first_inject milestone } overlay: { duration: 1 } -- name: First inject selection - auto: False - organization: Org 2 - alternatives: - - name: First inject - control: { activate_milestone: inject_selection } - content: - content: | - This is the first inject. - NOTE TO INSTR - It is multiline and contains a file. - file_name: file1.txt - - - name: Second inject - content: - content: | - This is the second inject. - NOTE TO INSTR 2 - It is multiline and contains a file. - file_name: file1.txt - - name: Markdown inject - content: { content_path: simple.md } - -- name: Final inject selection - auto: False - alternatives: - - name: Evaluation great - control: { activate_milestone: evaluation_great } - content: { content: Good job, you finished the exercise. } - - - name: Evaluation bad - control: { activate_milestone: evaluation_bad } - content: { content: You finished the exercise in a wrong way. } - - name: Markdown lists showcase inject organization: Org 2 - auto: True time: 1 alternatives: - name: Auto markdown inject with lists @@ -51,7 +15,6 @@ - name: Markdown links showcase inject organization: Org 2 - auto: True time: 2 alternatives: - name: Auto markdown inject with links @@ -59,7 +22,6 @@ - name: Markdown text formatting showcase inject organization: Org 2 - auto: True time: 3 alternatives: - name: Auto markdown inject with text formatting @@ -67,7 +29,6 @@ - name: Delayed first inject organization: Org 1 - auto: True delay: 1 alternatives: - name: Delayed first inject @@ -75,14 +36,12 @@ - name: Timed inject - auto: True time: 3 alternatives: - name: Timed inject content: { content: This is a timed inject which should arrive after 3 minutes. } - name: Conditional inject - auto: True time: 5 alternatives: - name: 1 Inject @@ -98,7 +57,6 @@ control: { milestone_condition: evaluation_great } - name: Auto timed inject depends on website_traffic_blocked milestone - auto: True time: 6 alternatives: - name: Inject option when not website_traffic_blocked @@ -110,7 +68,6 @@ content: { content: Website was blocked., file_name: website-root.jpg } - name: Timed inject when website visited but not blocked sending zip - auto: True organization: Org 1 time: 10 alternatives: diff --git a/definitions/emails_definition/config.yml b/definitions/emails_definition/config.yml index e7bd76d1c6394524de9f7fac82c0466d5c1aad64..3ce1d7f550a291e5beec0e3c2d69d0d73d920873 100644 --- a/definitions/emails_definition/config.yml +++ b/definitions/emails_definition/config.yml @@ -1,4 +1,4 @@ exercise_duration: 60 email_between_teams: True show_exercise_time: False -version: 0.8.0 +version: 0.9.0 diff --git a/definitions/emails_definition/injects/after-3-min.yml b/definitions/emails_definition/injects/after-3-min.yml index c96918f8ba047ba7369ab66e31e0d71b6a780030..eef65efe5752b198fa8e2aa596e0b01eddc28d9e 100644 --- a/definitions/emails_definition/injects/after-3-min.yml +++ b/definitions/emails_definition/injects/after-3-min.yml @@ -1,5 +1,4 @@ - name: Timed inject - auto: True time: 3 alternatives: - name: Timed inject @@ -7,7 +6,6 @@ - name: Auto timed inject depends on website_traffic_blocked milestone - auto: True time: 6 type: email alternatives: @@ -24,7 +22,6 @@ content: { content: Website was blocked., file_name: website-root.jpg } - name: Timed inject when website visited but not blocked sending zip - auto: True time: 10 type: email alternatives: diff --git a/definitions/emails_definition/injects/before-3-min.yml b/definitions/emails_definition/injects/before-3-min.yml index ebf2f9f56e95481c5fc6e802647f265a94607c14..5ed4d4a42e0e66ef826f3f32c086881d9d7751fe 100644 --- a/definitions/emails_definition/injects/before-3-min.yml +++ b/definitions/emails_definition/injects/before-3-min.yml @@ -1,36 +1,11 @@ - name: First Inject - auto: True organization: Org 1 alternatives: - name: First inject control: { activate_milestone: first_inject } content: { content: first inject which activates first_inject milestone } -- name: First inject selection - auto: False - organization: Org 2 - alternatives: - - name: First inject - control: { activate_milestone: inject_selection } - content: - content: | - This is the first inject. - NOTE TO INSTR - It is multiline and contains a file. - file_name: file1.txt - - - name: Second inject - content: - content: | - This is the second inject. - NOTE TO INSTR 2 - It is multiline and contains a file. - file_name: file1.txt - - name: Markdown inject - content: { content_path: simple.md } - - name: Introductory inject email - auto: True type: email alternatives: - name: Introductory inject email @@ -45,7 +20,6 @@ overlay: { duration: 1 } - name: Repeated email - auto: True type: email alternatives: - name: Repeated email @@ -56,14 +30,12 @@ - name: Delayed first inject organization: Org 1 - auto: True delay: 1 alternatives: - name: Delayed first inject content: { content: This is the first delayed inject. } - name: Following email inject - auto: True time: 2 type: email alternatives: @@ -71,4 +43,3 @@ sender: introductoryaddress@mail.ex subject: Introductory email thread content: { content: This is the second auto email inject in the thread. } - diff --git a/definitions/roles_definition/config.yml b/definitions/roles_definition/config.yml index e3ba5f2370bae60a1b3df96a0ed48c1f23caee11..b9344056ebbadf50fb0285e79d5b6db6f950f453 100644 --- a/definitions/roles_definition/config.yml +++ b/definitions/roles_definition/config.yml @@ -1,4 +1,4 @@ exercise_duration: 60 show_exercise_time: False enable_roles: True -version: 0.8.0 +version: 0.9.0 diff --git a/definitions/roles_definition/injects.yml b/definitions/roles_definition/injects.yml index b03ecb54838ce96fd8c7f27c881c2ffc6ef78910..8b2e023b04e1d0758e4352f8fc3fe625f7dfd215 100644 --- a/definitions/roles_definition/injects.yml +++ b/definitions/roles_definition/injects.yml @@ -1,35 +1,12 @@ - name: First Inject - auto: True organization: Org 1 alternatives: - name: First inject control: { activate_milestone: first_inject } content: { content: first inject which activates first_inject milestone } -- name: First inject selection - auto: False - organization: Org 2 - alternatives: - - name: First inject - control: { activate_milestone: inject_selection } - content: - content: | - This is the first inject. - NOTE TO INSTR - It is multiline and contains a file. - file_name: file1.txt - - - name: Second inject - content: - content: | - This is the second inject. - NOTE TO INSTR 2 - It is multiline and contains a file. - file_name: file1.txt - - name: Delayed first inject organization: Org 1 - auto: True delay: 1 alternatives: - name: Delayed first inject @@ -37,7 +14,6 @@ - name: Timed inject - auto: True time: 3 alternatives: - name: Timed inject @@ -45,7 +21,6 @@ - name: Auto timed inject depends on website_traffic_blocked milestone - auto: True time: 6 alternatives: - name: Inject option when not website_traffic_blocked @@ -57,7 +32,6 @@ content: { content: Website was blocked., file_name: website-root.jpg } - name: Timed inject when website visited but not blocked sending zip - auto: True organization: Org 1 time: 10 alternatives: @@ -70,7 +44,6 @@ file_name: two_files.zip - name: Role dependent inject - auto: True alternatives: - name: Inject for role_1 content: { content: Hello role_1, file_name: file1.txt } @@ -85,21 +58,3 @@ content: { content: Hello role_3 } control: { roles: role_3 } overlay: { duration: 1 } - -- name: Role dependent inject selection - auto: False - alternatives: - - name: Inject for role_1 - content: { content: Selection for role_1, file_name: file1.txt } - control: { roles: role_1 } - - - name: Inject for role_2 - content: { content: Selection for role_2, file_name: file2.txt } - control: { roles: role_2 } - - - name: Inject for role_3 - content: { content: Selection for role_3 } - control: { roles: role_3 } - - - name: Inject for all roles - content: { content: Selection for all roles } diff --git a/exercise/migrations/0006_alter_teaminjectstate_status.py b/exercise/migrations/0006_alter_teaminjectstate_status.py new file mode 100644 index 0000000000000000000000000000000000000000..74c46f4021acd283c7e59cba3cb41a44967f2537 --- /dev/null +++ b/exercise/migrations/0006_alter_teaminjectstate_status.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.22 on 2024-07-10 15:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('exercise', '0005_auto_20240625_1241'), + ] + + operations = [ + migrations.AlterField( + model_name='teaminjectstate', + name='status', + field=models.IntegerField(choices=[(0, 'Unsent'), (2, 'Delayed'), (4, 'Sent')], default=0), + ), + ] diff --git a/exercise/models.py b/exercise/models.py index db1fc8e3c82038104ed7cdb61df5a3de2f2cbc95..5e0e5018c8427d61a90dd6f298585b83a0b51afa 100644 --- a/exercise/models.py +++ b/exercise/models.py @@ -137,8 +137,7 @@ class TeamInjectState(models.Model): class Status(models.IntegerChoices): UNSENT = 0 DELAYED = 2 - SELECTION = 4 - SENT = 6 + SENT = 4 team = models.ForeignKey( Team, on_delete=models.CASCADE, related_name="inject_states" diff --git a/exercise/schema/query.py b/exercise/schema/query.py index 64bc2b78c46248f4ffac8bfc76deed36ca8af528..31d36ce8f8c52c99ebd61989ad7a45c0fd0bfa22 100644 --- a/exercise/schema/query.py +++ b/exercise/schema/query.py @@ -1,8 +1,9 @@ from typing import Optional, List -import graphene +import graphene from django.conf import settings +from aai.utils import protected, extra_protected, Check from common_lib.schema_types import ( ExerciseType, DefinitionType, @@ -21,7 +22,6 @@ from exercise_definition.models import ( Channel, ) from user.models import User -from aai.utils import protected, extra_protected, Check class Query(graphene.ObjectType): @@ -58,10 +58,10 @@ class Query(graphene.ObjectType): definition_id=graphene.ID(required=True), description="Retrieve a specific definition", ) - auto_injects = graphene.List( + injects = graphene.List( DefinitionInjectType, exercise_id=graphene.ID(required=True), - description="Retrieve all auto inject categories", + description="Retrieve all automatic injects", ) milestones = graphene.List( @@ -156,15 +156,8 @@ class Query(graphene.ObjectType): @protected(User.AuthGroup.INSTRUCTOR) @extra_protected(Check.EXERCISE_ID) - def resolve_auto_injects(self, info, exercise_id: str) -> List[Inject]: - exercise = get_model( - Exercise, - id=int(exercise_id), - ) - - return Inject.objects.filter( - definition_id=exercise.definition.id, auto=True - ) + def resolve_injects(self, info, exercise_id: str) -> List[Inject]: + return Inject.objects.filter(definition__exercises__in=[exercise_id]) @protected(User.AuthGroup.INSTRUCTOR) @extra_protected(Check.EXERCISE_ID) diff --git a/exercise_definition/lib/definition_uploader.py b/exercise_definition/lib/definition_uploader.py index 6e818c7ac386aff297e04adc0430d69bfd50a399..1293eb066169dbf39d03a9ec964feb9906a721ce 100644 --- a/exercise_definition/lib/definition_uploader.py +++ b/exercise_definition/lib/definition_uploader.py @@ -100,7 +100,6 @@ def _convert_to_inject( ) -> Inject: return Inject.objects.create( name=yaml_inject.name, - auto=yaml_inject.auto, time=yaml_inject.time, delay=yaml_inject.delay, organization=yaml_inject.organization, diff --git a/exercise_definition/migrations/0005_remove_inject_auto.py b/exercise_definition/migrations/0005_remove_inject_auto.py new file mode 100644 index 0000000000000000000000000000000000000000..bdf2abdf873bb470af0202db35f9e6b9620c2477 --- /dev/null +++ b/exercise_definition/migrations/0005_remove_inject_auto.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.22 on 2024-07-10 15:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('exercise_definition', '0004_auto_20240625_1239'), + ] + + operations = [ + migrations.RemoveField( + model_name='inject', + name='auto', + ), + ] diff --git a/exercise_definition/models/models.py b/exercise_definition/models/models.py index 915247a0d7240d49f53b379fc815b0d03df3707d..b1bfaea5263fab995005759e0cc5a3c8293f2f59 100644 --- a/exercise_definition/models/models.py +++ b/exercise_definition/models/models.py @@ -266,7 +266,6 @@ Alternative = Union["InfoAlternative", "EmailAlternative"] class Inject(models.Model): name = models.TextField() - auto = models.BooleanField() time = models.IntegerField() delay = models.IntegerField() organization = models.TextField() diff --git a/exercise_definition/models/yaml_models.py b/exercise_definition/models/yaml_models.py index 7f6c291efb27499cd5a03ad781c5382ec41e5fa8..5a72debce99e164e2bcee09f6f370a10b44e1ec9 100644 --- a/exercise_definition/models/yaml_models.py +++ b/exercise_definition/models/yaml_models.py @@ -178,7 +178,6 @@ INJECT_TYPE_MAPPING: Dict[str, Type[Yamlizable]] = { class YamlInject(Object): name = Attribute(type=str) - auto = Attribute(type=bool) time = Attribute(type=int, default=0) delay = Attribute(type=int, default=0) organization = Attribute(type=str, default="") diff --git a/exercise_definition/serializers.py b/exercise_definition/serializers.py index d2f47182bfb5315cfd2e87348235d3de7d1e479a..13ee6b136237e9a2c2db683c11af7a0617cd51be 100644 --- a/exercise_definition/serializers.py +++ b/exercise_definition/serializers.py @@ -110,7 +110,6 @@ class InjectSerializer(ModelSerializer): fields = [ "inject_id", "name", - "auto", "time", "delay", "organization", diff --git a/exercise_definition/tests/validation/inject_validation_test.py b/exercise_definition/tests/validation/inject_validation_test.py index 65e7e67f48d0fa6aca45d42f49d71039ce928f87..966387649ae981f52c23020d5758319edd64091a 100644 --- a/exercise_definition/tests/validation/inject_validation_test.py +++ b/exercise_definition/tests/validation/inject_validation_test.py @@ -9,7 +9,6 @@ class InjectValidationTestCase(TestCase): inject: YamlInject = YamlInject.load( """ name: inject name - auto: True type: email alternatives: - name: some name @@ -27,7 +26,6 @@ class InjectValidationTestCase(TestCase): inject: YamlInject = YamlInject.load( """ name: inject name - auto: True type: email alternatives: - name: some name diff --git a/running_exercise/lib/exercise_updater.py b/running_exercise/lib/exercise_updater.py index 69c61f5a98cc663b641f655bbf3b3a49cd1d3335..e1e714bf22e5657653723baac974cafde4c8da29 100644 --- a/running_exercise/lib/exercise_updater.py +++ b/running_exercise/lib/exercise_updater.py @@ -26,8 +26,6 @@ from running_exercise.lib.milestone_handler import ( ) from running_exercise.lib.utils import create_action_log, check_control from running_exercise.models import ( - InjectOption, - InjectSelection, InjectDetails, Email, ActionLog, @@ -42,43 +40,6 @@ def _check_conditions(inject_state: TeamInjectState) -> List[Alternative]: ] -def _convert_to_inject_option(alternative: Alternative) -> InjectOption: - if isinstance(alternative, EmailAlternative): - return InjectOption( - name=alternative.name, - sender=alternative.sender, - control=alternative.control, - content=alternative.content, - ) - - # This MUST go - return InjectOption( - name=alternative.name, - control=alternative.control, - content=alternative.content, - ) - - -# TODO: Inject selections will be reworked later -def _send_selection(inject_state: TeamInjectState, injects: List[Alternative]): - options = [_convert_to_inject_option(inject) for inject in injects] - - selection = InjectSelection.objects.create( - team=inject_state.team, - name=inject_state.inject.name, - timestamp=timezone.now(), - team_inject=inject_state, - ) - - for option in options: - option.inject_selection = selection - option.save() - - inject_state.status = TeamInjectState.Status.SELECTION - inject_state.save() - SubscriptionHandler.broadcast_inject_selection(selection, inject_state.team) - - def _send_inject(inject_state: TeamInjectState, channels: Dict[str, Channel]): """ Send the selected inject to the team and mark the category as sent. @@ -142,10 +103,6 @@ def _check_inject(inject_state: TeamInjectState, seconds: int) -> bool: if len(sendable_alternatives) == 0: return False - if not inject_state.inject.auto: - _send_selection(inject_state, sendable_alternatives) - return False - # only one inject can be sent from a category, priority is determined by the name # the injects in the db are already ordered by name, therefore the first inject # has the priority @@ -193,7 +150,7 @@ class ExerciseUpdater: injects = TeamInjectState.objects.filter( team__exercise=self.exercise, inject__time__lte=exercise_minutes, - status__lt=TeamInjectState.Status.SELECTION, + status__lt=TeamInjectState.Status.SENT, ).prefetch_related("inject", "team") for inject in injects: # type: TeamInjectState diff --git a/running_exercise/lib/inject_selector.py b/running_exercise/lib/inject_selector.py deleted file mode 100644 index d1df40e574a3262d8fb206ea37a2be7c4c724022..0000000000000000000000000000000000000000 --- a/running_exercise/lib/inject_selector.py +++ /dev/null @@ -1,118 +0,0 @@ -from typing import Optional - -from markdown import markdown - -from common_lib.exceptions import RunningExerciseOperationException -from common_lib.utils import get_model -from exercise.models import Team, EmailParticipant -from exercise_definition.models import ( - FileInfo, - Content, - Control, - Channel, - InjectTypes, -) -from running_exercise.graphql_inputs import ( - SelectTeamInjectInput, -) -from running_exercise.lib.email_client import EmailClient, send_email -from running_exercise.lib.milestone_handler import update_milestones -from running_exercise.lib.utils import create_action_log -from running_exercise.models import ( - InjectSelection, - InjectDetails, - CustomInjectDetails, - Email, -) -from user.models import User - -CUSTOM_INJECT_SELECTION_ID = "-1" - - -class InjectSelector: - @staticmethod - def select_inject( - select_team_inject_input: SelectTeamInjectInput, - user: Optional[User] = None, - ): - team = get_model(Team, id=select_team_inject_input.team_id) - if select_team_inject_input.file_id != "": - file_info = get_model(FileInfo, id=select_team_inject_input.file_id) - else: - file_info = None - - selection: Optional[InjectSelection] = None - if select_team_inject_input.selection_id != CUSTOM_INJECT_SELECTION_ID: - selection = get_model( - InjectSelection, id=int(select_team_inject_input.selection_id) - ) - if selection.team_id != team.id: - raise RunningExerciseOperationException( - "Invalid selection attempt, team has no inject selection " - "with this id" - ) - - InjectSelection.objects.filter( - pk=select_team_inject_input.selection_id - ).delete() - - update_milestones( - team, - Control( - activate_milestone=select_team_inject_input.activate_milestone, - deactivate_milestone=select_team_inject_input.deactivate_milestone, - ), - ) - - # unfortunately, there is currently no good way to tell which inject option this came from, - # if any, which prevents us from using the pre-rendered markdown, - # furthermore, we do not know if the content has been modified - # the easiest way to handle this is to consider all content to be modified, - # and pass it to the renderer, this MUST be reworked at some point in the future - content = Content.objects.create( - raw=select_team_inject_input.content, - rendered=markdown(select_team_inject_input.content), - file_info=file_info, - ) - if select_team_inject_input.option_email: - sender = get_model( - EmailParticipant, - team.exercise.email_participants, - address=select_team_inject_input.sender, - ) - thread = EmailClient.get_thread( - team, - select_team_inject_input.sender, - select_team_inject_input.subject, - ) - channel = get_model( - Channel, - sender.exercise.definition.channels, - type=InjectTypes.EMAIL, - ) - email = Email.objects.create( - thread=thread, - sender=sender, - content=content, - ) - - for _ in range(select_team_inject_input.repeat + 1): - send_email(email=email, channel=channel) - else: - channel = get_model( - Channel, - team.exercise.definition.channels, - type=InjectTypes.INFO, - ) - if selection is not None: - details = InjectDetails.objects.create( - inject=selection.team_inject.inject, - content=content, - ) - else: - details = CustomInjectDetails.objects.create( - content=content, - user=user, - ) - for _ in range(select_team_inject_input.repeat + 1): - create_action_log(team, details, channel) diff --git a/running_exercise/migrations/0006_auto_20240712_1028.py b/running_exercise/migrations/0006_auto_20240712_1028.py new file mode 100644 index 0000000000000000000000000000000000000000..fa789154d675e755582d0a2a2e1773d2815ff4c2 --- /dev/null +++ b/running_exercise/migrations/0006_auto_20240712_1028.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.22 on 2024-07-12 10:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('running_exercise', '0005_custominjectdetails_overlay'), + ] + + operations = [ + migrations.RemoveField( + model_name='injectselection', + name='team', + ), + migrations.RemoveField( + model_name='injectselection', + name='team_inject', + ), + migrations.DeleteModel( + name='InjectOption', + ), + migrations.DeleteModel( + name='InjectSelection', + ), + ] diff --git a/running_exercise/models.py b/running_exercise/models.py index 5e7ad3b3f13197819cfc834df249182e902e94e4..095916f8fe34b8b29799e980935d949022da60ce 100644 --- a/running_exercise/models.py +++ b/running_exercise/models.py @@ -211,39 +211,6 @@ class ThreadParticipant(models.Model): default_permissions = () -class InjectSelection(models.Model): - name = models.TextField() - timestamp = models.DateTimeField(null=True, blank=True) - team = models.ForeignKey( - Team, on_delete=models.CASCADE, related_name="inject_selections" - ) - team_inject = models.ForeignKey( - TeamInjectState, on_delete=models.CASCADE, related_name="selections" - ) - - class Meta: - ordering = ["id"] - default_permissions = () - - -class InjectOption(models.Model): - name = models.TextField() - sender = models.TextField(default="") - control = models.ForeignKey( - Control, on_delete=models.CASCADE, related_name="+" - ) - content = models.ForeignKey( - Content, on_delete=models.CASCADE, related_name="+" - ) - inject_selection = models.ForeignKey( - InjectSelection, on_delete=models.CASCADE, related_name="options" - ) - - class Meta: - ordering = ["id"] - default_permissions = () - - class TeamFile(models.Model): team = models.ForeignKey( Team, on_delete=models.CASCADE, related_name="uploaded_files" diff --git a/running_exercise/schema/mutation.py b/running_exercise/schema/mutation.py index 7f3d6f9545ec402850ac4304bcf9bc73190a7266..eb86b1268c0aca6d54b35e72250d8bc4ba24b523 100644 --- a/running_exercise/schema/mutation.py +++ b/running_exercise/schema/mutation.py @@ -7,7 +7,6 @@ from aai.utils import protected, extra_protected, input_object_protected, Check from common_lib.logger import logger from common_lib.schema_types import ExerciseType, EmailThreadType from running_exercise.graphql_inputs import ( - SelectTeamInjectInput, UseToolInput, SendEmailInput, QuestionnaireInput, @@ -15,9 +14,6 @@ from running_exercise.graphql_inputs import ( ) from running_exercise.lib.email_client import EmailClient from running_exercise.lib.exercise_loop import ExerciseLoop -from running_exercise.lib.inject_selector import ( - InjectSelector, -) from running_exercise.lib.instructor_action_handler import ( InstructorActionHandler, ) @@ -47,25 +43,6 @@ class UseToolMutation(graphene.Mutation): return UseToolMutation(operation_done=True) -class SelectTeamInjectOptionMutation(graphene.Mutation): - class Arguments: - select_team_inject_input = graphene.Argument( - SelectTeamInjectInput, required=True - ) - - operation_done = graphene.Boolean() - - @classmethod - @protected(User.AuthGroup.INSTRUCTOR) - @input_object_protected("select_team_inject_input") - def mutate( - cls, root, info, select_team_inject_input: SelectTeamInjectInput - ) -> graphene.Mutation: - user = None if settings.NOAUTH else info.context.user - InjectSelector.select_inject(select_team_inject_input, user) - return SelectTeamInjectOptionMutation(operation_done=True) - - class CreateThreadMutation(graphene.Mutation): class Arguments: participant_addresses = graphene.List( @@ -219,9 +196,6 @@ class Mutation(graphene.ObjectType): use_tool = UseToolMutation.Field( description="Mutation for performing a tool action" ) - select_team_inject_option = SelectTeamInjectOptionMutation.Field( - description="Mutation for selecting an inject for a specific team" - ) move_time = MoveExerciseTimeMutation.Field( description="Mutation for moving the in-exercise time by the specified amount of seconds" ) diff --git a/running_exercise/schema/query.py b/running_exercise/schema/query.py index 503fb66e488e82b465521ceaca69395201125892..82efe25eb5cfe614c0edfeb907b4322181acfae8 100644 --- a/running_exercise/schema/query.py +++ b/running_exercise/schema/query.py @@ -9,7 +9,6 @@ from common_lib.exceptions import RunningExerciseOperationException from common_lib.schema_types import ( ActionLogType, MilestoneStateType, - InjectSelectionType, EmailThreadType, TeamLearningObjectiveType, ToolType, @@ -39,7 +38,7 @@ from running_exercise.lib.email_client import EmailClient from running_exercise.lib.exercise_loop import ExerciseLoop from running_exercise.lib.milestone_handler import get_milestone_states from running_exercise.lib.utils import get_running_exercise -from running_exercise.models import ActionLog, InjectSelection, EmailThread +from running_exercise.models import ActionLog, EmailThread class Query(graphene.ObjectType): @@ -88,11 +87,6 @@ class Query(graphene.ObjectType): ), description="Retrieve all milestones for the specific team filtered by team visibility", ) - team_inject_selections = graphene.List( - InjectSelectionType, - team_id=graphene.ID(required=True), - description="Retrieve all available inject selections for the specific team. Useful for the instructor view.", - ) email_contacts = graphene.List( EmailParticipantType, visible_only=graphene.Boolean( @@ -266,13 +260,6 @@ class Query(graphene.ObjectType): ) -> List[MilestoneState]: return get_milestone_states(int(team_id), visible_only) - @protected(User.AuthGroup.INSTRUCTOR) - @extra_protected(Check.TEAM_ID) - def resolve_team_inject_selections( - self, info, team_id: str - ) -> QuerySet[InjectSelection]: - return InjectSelection.objects.filter(team_id=team_id) - @protected(User.AuthGroup.TRAINEE) @extra_protected(Check.VISIBLE_ONLY) def resolve_email_contacts( diff --git a/running_exercise/subscription.py b/running_exercise/subscription.py index 5e4a93badd9b18b9373f2345c589d75206312662..68f1078dd62b2e7edd8f7a9992fb4f59007b0089 100644 --- a/running_exercise/subscription.py +++ b/running_exercise/subscription.py @@ -6,7 +6,6 @@ from aai.utils import protected, extra_protected, Check from common_lib.schema_types import ( ActionLogType, MilestoneStateType, - InjectSelectionType, EmailThreadType, TeamQuestionnaireStateType, ) @@ -81,25 +80,6 @@ class MilestonesSubscription(channels_graphql_ws.Subscription): return MilestonesSubscription(milestones=payload) -class InjectSelectionsSubscription(channels_graphql_ws.Subscription): - notification_queue_limit = NOTIFICATION_QUEUE_LIMIT - inject_selection = graphene.Field(InjectSelectionType) - - class Arguments: - team_id = graphene.ID() - - @staticmethod - @protected(User.AuthGroup.INSTRUCTOR) - @extra_protected(Check.TEAM_ID) - def subscribe(root, info, team_id: str): - get_model(Team, id=int(team_id)) - return [team_id] - - @staticmethod - def publish(payload, info, team_id: str): - return InjectSelectionsSubscription(inject_selection=payload) - - class EmailThreadSubscription(channels_graphql_ws.Subscription): notification_queue_limit = NOTIFICATION_QUEUE_LIMIT email_thread = graphene.Field(EmailThreadType) @@ -206,9 +186,6 @@ class Subscription(graphene.ObjectType): milestones = MilestonesSubscription.Field( description="Subscription notifies about changes in milestone states" ) - inject_selections = InjectSelectionsSubscription.Field( - description="Subscription notifies about new inject selections available for the specific team" - ) email_threads = EmailThreadSubscription.Field( description="Subscription notifies about new threads and new emails for the specific team" ) diff --git a/running_exercise/tests/exercise_updater_tests.py b/running_exercise/tests/exercise_updater_tests.py index 0ed533f14dc232dcde618581115badc1afe24d98..5c631f3b35f66cb00302e856df793a89026a8762 100644 --- a/running_exercise/tests/exercise_updater_tests.py +++ b/running_exercise/tests/exercise_updater_tests.py @@ -72,25 +72,6 @@ class ExerciseUpdaterTestCase(TestCase): ) self.assertIsNotNone(sendable_alternatives[0].overlay) - inject_state = team.inject_states.get( - inject__name="First inject selection" - ) - sendable_alternatives = _check_conditions(inject_state) - - self.assertEqual(len(sendable_alternatives), 3) - - for alternative in sendable_alternatives: - if alternative.name == "Markdown inject": - continue - if alternative.name == "First inject": - self.assertEqual( - alternative.control.activate_milestone, "inject_selection" - ) - - self.assertEqual( - alternative.content.file_info.file_name, "file1.txt" - ) - inject_state = team.inject_states.get( inject__name="Auto timed inject depends on website_traffic_blocked milestone" ) @@ -142,16 +123,6 @@ class ExerciseUpdaterTestCase(TestCase): self.assertEqual(sendable_alternatives[0].name, "Inject for role_1") self.assertIsNotNone(sendable_alternatives[0].overlay) - inject_state = role_1.inject_states.get( - inject__name="Role dependent inject selection" - ) - sendable_alternatives = _check_conditions(inject_state) - self.assertEqual(len(sendable_alternatives), 2) - for alternative in sendable_alternatives: - self.assertIn( - alternative.name, ["Inject for role_1", "Inject for all roles"] - ) - role_2 = self.roles_exercise.teams.get(role="role_2") inject_state = role_2.inject_states.get( inject__name="Role dependent inject" @@ -161,31 +132,9 @@ class ExerciseUpdaterTestCase(TestCase): self.assertEqual(sendable_alternatives[0].name, "Inject for role_2") self.assertIsNone(sendable_alternatives[0].overlay) - inject_state = role_2.inject_states.get( - inject__name="Role dependent inject selection" - ) - sendable_alternatives = _check_conditions(inject_state) - self.assertEqual(len(sendable_alternatives), 2) - for alternative in sendable_alternatives: - self.assertIn( - alternative.name, ["Inject for role_2", "Inject for all roles"] - ) - def test_check_category_base(self): team: Team = self.base_exercise.teams.first() - inject_state = team.inject_states.get( - inject__name="First inject selection" - ) - - self.assertFalse( - _check_inject(inject_state, 0), - msg="Inject should be ready to be sent", - ) - - self.assertEqual(inject_state.status, TeamInjectState.Status.SELECTION) - self.assertEqual(team.inject_selections.count(), 1) - inject_state = team.inject_states.get(inject__name="Conditional inject") website_visited = self.base_definition.milestones.get( name="website_visited"