def test_get_activity_executions(self, get_nodes_mock):
        get_nodes_mock.return_value = {
            'nodes': [{
                'id': 1,
                'labels': ['Activity Execution'],
                'properties': [{
                    'key': 'test',
                    'value': 'test'
                }]
            }, {
                'id': 2,
                'labels': ['Activity Execution'],
                'properties': [{
                    'key': 'test2',
                    'value': 'test3'
                }]
            }]
        }
        activity_execution_one = BasicActivityExecutionOut(
            additional_properties=[PropertyIn(key='test', value='test')], id=1)
        activity_execution_two = BasicActivityExecutionOut(
            additional_properties=[PropertyIn(key='test2', value='test3')],
            id=2)
        activity_executions = ActivityExecutionsOut(activity_executions=[
            activity_execution_one, activity_execution_two
        ])
        activity_executions_service = ActivityExecutionService()

        result = activity_executions_service.get_activity_executions()

        self.assertEqual(result, activity_executions)
        get_nodes_mock.assert_called_once_with("`Activity Execution`")
    def test_update_activity_execution_without_error(
            self, get_node_relationships_mock, delete_node_properties_mock,
            get_node_mock, create_properties_mock):
        id_node = 1
        create_properties_mock.return_value = {}
        delete_node_properties_mock.return_value = {}
        get_node_relationships_mock.return_value = {
            "relationships": [{
                "start_node": id_node,
                "end_node": 19,
                "name": "testRelation",
                "id": 0,
                "properties": None
            }, {
                "start_node": 15,
                "end_node": id_node,
                "name": "testReversedRelation",
                "id": 0,
                "properties": None
            }]
        }
        get_node_mock.return_value = {
            'id': id_node,
            'labels': ['Activity Execution'],
            'properties': [{
                'key': 'identifier',
                'value': 5
            }],
            "errors": None,
            'links': None
        }
        additional_properties = [PropertyIn(key='identifier', value=5)]
        activity_execution_in = ActivityExecutionPropertyIn(
            id=id_node, additional_properties=additional_properties)
        activity_execution_out = ActivityExecutionOut(
            additional_properties=additional_properties,
            id=id_node,
            relations=[
                RelationInformation(second_node_id=19,
                                    name="testRelation",
                                    relation_id=0)
            ],
            reversed_relations=[
                RelationInformation(second_node_id=15,
                                    name="testReversedRelation",
                                    relation_id=0)
            ])
        calls = [mock.call(1)]
        activity_execution_service = ActivityExecutionService()

        result = activity_execution_service.update_activity_execution(
            id_node, activity_execution_in)

        self.assertEqual(result, activity_execution_out)
        get_node_mock.assert_has_calls(calls)
        create_properties_mock.assert_called_once_with(id_node,
                                                       activity_execution_in)
        get_node_relationships_mock.assert_called_once_with(id_node)
    def test_get_activity_executions_empty(self, get_nodes_mock):
        get_nodes_mock.return_value = {'nodes': []}
        activity_executions = ActivityExecutionsOut(activity_execution=[])
        activity_executions_service = ActivityExecutionService()

        result = activity_executions_service.get_activity_executions()

        self.assertEqual(result, activity_executions)
        get_nodes_mock.assert_called_once_with("`Activity Execution`")
示例#4
0
    def test_save_activity_execution_with_node_error(self, create_node_mock):
        id_node = 1
        create_node_mock.return_value = {'id': id_node, 'properties': None, "errors": ['error'], 'links': None}
        activity_execution = ActivityExecutionIn(activity_id=2, arrangement_id=3)
        activity_execution_service = ActivityExecutionService()

        result = activity_execution_service.save_activity_execution(activity_execution)

        self.assertEqual(result, ActivityExecutionOut(errors=['error']))
        create_node_mock.assert_called_once_with('`Activity Execution`')
    def test_delete_activity_execution_with_error(self, get_node_mock):
        id_node = 1
        get_node_mock.return_value = {
            'id': id_node,
            'errors': ['error'],
            'links': None
        }
        not_found = NotFoundByIdModel(id=id_node, errors=['error'])
        activity_execution_service = ActivityExecutionService()

        result = activity_execution_service.delete_activity_execution(id_node)

        self.assertEqual(result, not_found)
        get_node_mock.assert_called_once_with(id_node)
    def test_get_activity_execution_without_label(self, get_node_mock):
        id_node = 1
        get_node_mock.return_value = {
            'id': id_node,
            'labels': ['Test'],
            'properties': None,
            "errors": None,
            'links': None
        }
        not_found = NotFoundByIdModel(id=id_node, errors="Node not found.")
        activity_execution_service = ActivityExecutionService()

        result = activity_execution_service.get_activity_execution(id_node)

        self.assertEqual(result, not_found)
        get_node_mock.assert_called_once_with(id_node)
    def test_delete_activity_execution_without_error(
            self, get_node_relationships_mock, get_node_mock,
            delete_node_mock):
        id_node = 1
        delete_node_mock.return_value = get_node_mock.return_value = {
            'id': id_node,
            'labels': ['Activity Execution'],
            'properties': [],
            "errors": None,
            'links': None
        }
        get_node_relationships_mock.return_value = {
            "relationships": [{
                "start_node": id_node,
                "end_node": 19,
                "name": "testRelation",
                "id": 0,
                "properties": None
            }, {
                "start_node": 15,
                "end_node": id_node,
                "name": "testReversedRelation",
                "id": 0,
                "properties": None
            }]
        }
        activity_execution = ActivityExecutionOut(
            additional_properties=[],
            id=id_node,
            relations=[
                RelationInformation(second_node_id=19,
                                    name="testRelation",
                                    relation_id=0)
            ],
            reversed_relations=[
                RelationInformation(second_node_id=15,
                                    name="testReversedRelation",
                                    relation_id=0)
            ])
        activity_execution_service = ActivityExecutionService()

        result = activity_execution_service.delete_activity_execution(id_node)

        self.assertEqual(result, activity_execution)
        get_node_mock.assert_called_once_with(id_node)
        delete_node_mock.assert_called_once_with(id_node)
    def test_update_activity_execution_with_error(self, get_node_mock):
        id_node = 1
        get_node_mock.return_value = {
            'id': id_node,
            'errors': ['error'],
            'links': None
        }
        not_found = NotFoundByIdModel(id=id_node, errors=['error'])
        additional_properties = [PropertyIn(key='identifier', value=5)]
        activity_execution_in = ActivityExecutionPropertyIn(
            id=id_node, additional_properties=additional_properties)
        activity_execution_service = ActivityExecutionService()

        result = activity_execution_service.update_activity_execution(
            id_node, activity_execution_in)

        self.assertEqual(result, not_found)
        get_node_mock.assert_called_once_with(id_node)
示例#9
0
    def test_save_activity_execution_without_errors(self, get_node_relationships_mock, get_node_mock,
                                                    create_relationships_mock, create_properties_mock,
                                                    create_node_mock):
        id_node = 1
        get_node_mock.return_value = {'id': id_node, 'labels': ['Activity Execution'],
                                      'properties': [],
                                      "errors": None, 'links': None}
        get_node_relationships_mock.return_value = {"relationships": [
            {"start_node": id_node, "end_node": 19,
             "name": "testRelation", "id": 0,
             "properties": None},
            {"start_node": 15, "end_node": id_node,
             "name": "testReversedRelation", "id": 0,
             "properties": None}]}
        create_node_mock.return_value = {'id': id_node, 'properties': None, "errors": None, 'links': None}
        create_properties_mock.return_value = {'id': id_node, 'errors': None, 'links': None}
        create_relationships_mock.return_value = {'start_node': 1, 'end_node': 2,
                                                  'name': 'hasParticipant', 'errors': None}
        additional_properties = []
        activity_execution_in = ActivityExecutionIn(activity_id=2, arrangement_id=3)
        activity_execution_out = ActivityExecutionOut(additional_properties=additional_properties, relations=
                                                      [RelationInformation(second_node_id=19, name="testRelation",
                                                                           relation_id=0)],
                                                      reversed_relations=
                                                      [RelationInformation(second_node_id=15,
                                                                           name="testReversedRelation",
                                                                           relation_id=0)], id=id_node)
        calls = [mock.call(2), mock.call(3), mock.call(1)]
        activity_execution_service = ActivityExecutionService()

        result = activity_execution_service.save_activity_execution(activity_execution_in)

        self.assertEqual(result, activity_execution_out)
        create_node_mock.assert_called_once_with('`Activity Execution`')
        # create_properties_mock.assert_not_called()
        create_relationships_mock.assert_not_called()
        get_node_mock.assert_has_calls(calls)
示例#10
0
class ParticipationService:
    """
    Object to handle logic of participation requests

    Attributes:
    graph_api_service (GraphApiService): Service used to communicate with Graph API
    activity_execution_service (ActivityExecutionService): Service to send activity execution requests
    participant_state_service (ParticipantStateService): Service to send participant state requests
    """
    graph_api_service = GraphApiService()
    activity_execution_service = ActivityExecutionService()
    participant_state_service = ParticipantStateService()

    def save_participation(self, participation: ParticipationIn):
        """
        Send request to graph api to create new participation

        Args:
            participation (ParticipationIn): Participation to be added

        Returns:
            Result of request as participation object
        """
        node_response = self.graph_api_service.create_node("Participation")

        if node_response["errors"] is not None:
            return ParticipationOut(errors=node_response["errors"])

        participation_id = node_response["id"]

        if participation.activity_execution_id is not None and \
                type(self.activity_execution_service.get_activity_execution(participation.activity_execution_id)) \
                is not NotFoundByIdModel:
            self.graph_api_service.create_relationships(
                participation_id, participation.activity_execution_id,
                "hasActivityExecution")

        if participation.participant_state_id is not None and \
                type(self.participant_state_service.get_participant_state(participation.participant_state_id)) \
                is not NotFoundByIdModel:
            self.graph_api_service.create_relationships(
                participation_id, participation.participant_state_id,
                "hasParticipantState")

        return self.get_participation(participation_id)

    def get_participations(self):
        """
        Send request to graph api to get participations
        Returns:
            Result of request as list of participation objects
        """
        get_response = self.graph_api_service.get_nodes("Participation")

        participations = []

        for participation_node in get_response["nodes"]:
            properties = {'id': participation_node['id']}
            participation = BasicParticipationOut(**properties)
            participations.append(participation)

        return ParticipationsOut(participations=participations)

    def get_participation(self, participation_id: int):
        """
        Send request to graph api to get given participation
        Args:
            participation_id (int): Id of participation
        Returns:
            Result of request as participation object
        """
        get_response = self.graph_api_service.get_node(participation_id)

        if get_response["errors"] is not None:
            return NotFoundByIdModel(id=participation_id,
                                     errors=get_response["errors"])
        if get_response["labels"][0] != "Participation":
            return NotFoundByIdModel(id=participation_id,
                                     errors="Node not found.")

        participation = {
            'id': get_response['id'],
            'relations': [],
            'reversed_relations': []
        }

        relations_response = self.graph_api_service.get_node_relationships(
            participation_id)

        for relation in relations_response["relationships"]:
            if relation["start_node"] == participation_id:
                participation['relations'].append(
                    RelationInformation(second_node_id=relation["end_node"],
                                        name=relation["name"],
                                        relation_id=relation["id"]))
            else:
                participation['reversed_relations'].append(
                    RelationInformation(second_node_id=relation["start_node"],
                                        name=relation["name"],
                                        relation_id=relation["id"]))

        return ParticipationOut(**participation)

    def delete_participation(self, participation_id: int):
        """
        Send request to graph api to delete given participation
        Args:
            participation_id (int): Id of participation
        Returns:
            Result of request as participation object
        """
        get_response = self.get_participation(participation_id)

        if type(get_response) is NotFoundByIdModel:
            return get_response

        self.graph_api_service.delete_node(participation_id)
        return get_response

    def update_participation_relationships(self, participation_id: int,
                                           participation: ParticipationIn):
        """
        Send request to graph api to update given participation relationships
        Args:
            participation_id (int): Id of participation
            participation (ParticipationIn): Relationships to update
        Returns:
            Result of request as participation object
        """
        get_response = self.get_participation(participation_id)

        if type(get_response) is NotFoundByIdModel:
            return get_response

        if participation.activity_execution_id is not None and \
                type(self.activity_execution_service.get_activity_execution(participation.activity_execution_id)) \
                is not NotFoundByIdModel:
            self.graph_api_service.create_relationships(
                participation_id, participation.activity_execution_id,
                "hasActivityExecution")

        if participation.participant_state_id is not None and \
                type(self.participant_state_service.get_participant_state(participation.participant_state_id)) \
                is not NotFoundByIdModel:
            self.graph_api_service.create_relationships(
                participation_id, participation.participant_state_id,
                "hasParticipantState")

        return self.get_participation(participation_id)
示例#11
0
class ActivityExecutionRouter:
    """
    Class for routing activity execution based requests

    Attributes:
    activity_execution_service (ActivityExecutionService): Service instance for activity execution
    """
    activity_execution_service = ActivityExecutionService()

    @router.post("/activity_executions",
                 tags=["activity executions"],
                 response_model=ActivityExecutionOut)
    async def create_activity_execution(
            self, activity_execution: ActivityExecutionIn, response: Response):
        """
        Create activity execution in database
        """
        create_response = self.activity_execution_service.save_activity_execution(
            activity_execution)
        if create_response.errors is not None:
            response.status_code = 422

        # add links from hateoas
        create_response.links = get_links(router)

        return create_response

    @router.get("/activity_executions",
                tags=["activity executions"],
                response_model=ActivityExecutionsOut)
    async def get_activity_executions(self, response: Response):
        """
        Get activity executions from database
        """

        get_response = self.activity_execution_service.get_activity_executions(
        )

        # add links from hateoas
        get_response.links = get_links(router)

        return get_response

    @router.get("/activity_executions/{activity_execution_id}",
                tags=["activity executions"],
                response_model=Union[ActivityExecutionOut, NotFoundByIdModel])
    async def get_activity_execution(self, activity_execution_id: int,
                                     response: Response):
        """
        Get activity executions from database
        """

        get_response = self.activity_execution_service.get_activity_execution(
            activity_execution_id)
        if get_response.errors is not None:
            response.status_code = 404

        # add links from hateoas
        get_response.links = get_links(router)

        return get_response

    @router.delete("/activity_executions/{activity_execution_id}",
                   tags=["activity executions"],
                   response_model=Union[ActivityExecutionOut,
                                        NotFoundByIdModel])
    async def delete_activity_execution(self, activity_execution_id: int,
                                        response: Response):
        """
        Delete activity executions from database
        """
        get_response = self.activity_execution_service.delete_activity_execution(
            activity_execution_id)
        if get_response.errors is not None:
            response.status_code = 404

        # add links from hateoas
        get_response.links = get_links(router)

        return get_response

    @router.put("/activity_executions/{activity_execution_id}",
                tags=["activity executions"],
                response_model=Union[ActivityExecutionOut, NotFoundByIdModel])
    async def update_activity_execution(
            self, activity_execution_id: int,
            activity_execution: ActivityExecutionPropertyIn,
            response: Response):
        """
        Update activity execution model in database
        """
        update_response = self.activity_execution_service.update_activity_execution(
            activity_execution_id, activity_execution)
        if update_response.errors is not None:
            response.status_code = 404

        # add links from hateoas
        update_response.links = get_links(router)

        return update_response

    @router.put("/activity_executions/{activity_execution_id}/relationships",
                tags=["activity executions"],
                response_model=Union[ActivityExecutionOut, NotFoundByIdModel])
    async def update_activity_execution_relationships(
            self, activity_execution_id: int,
            activity_execution: ActivityExecutionRelationIn,
            response: Response):
        """
        Update activity executions relations in database
        """
        update_response = self.activity_execution_service.update_activity_execution_relationships(
            activity_execution_id, activity_execution)
        if update_response.errors is not None:
            response.status_code = 404

        # add links from hateoas
        update_response.links = get_links(router)

        return update_response
示例#12
0
class ScenarioService:
    """
    Object to handle logic of scenarios requests

    Attributes:
    graph_api_service (GraphApiService): Service used to communicate with Graph API
    activity_execution_service (ActivityExecutionService): Service used to communicate with ActivityExecution
    experiment_service (ExperimentService): Service used to communicate with Experiment
    """
    graph_api_service = GraphApiService()
    activity_execution_service = ActivityExecutionService()
    experiment_service = ExperimentService()

    def save_scenario(self, scenario: ScenarioIn):
        """
        Send request to graph api to create new scenario

        Args:
            scenario (ScenarioIn): Scenario to be added

        Returns:
            Result of request as scenario object
        """
        activity_executions = [
            self.activity_execution_service.save_activity_execution(
                activity_execution=activity_execution)
            for activity_execution in scenario.activity_executions
        ]
        self.graph_api_service.create_relationships(scenario.experiment_id,
                                                    activity_executions[0].id,
                                                    'hasScenario')
        [
            self.graph_api_service.create_relationships(
                activity_executions[index - 1].id,
                activity_executions[index].id, 'nextActivityExecution')
            for index in range(1, len(activity_executions))
        ]

        return ScenarioOut(experiment_id=scenario.experiment_id,
                           activity_executions=activity_executions)

    def add_activity_execution(self, previous_id: int,
                               activity_execution: ActivityExecutionIn):
        """
        Send request to graph api to add activity_execution to scenario

        Args:
            previous_id (int): Id of previous activity_execution or experiment
            activity_execution (ActivityExecutionIn): ActivityExecution to be added

        Returns:
            Result of request as activity_execution object
        """
        relationships = self.graph_api_service.get_node_relationships(
            previous_id)['relationships']
        activity_execution_result = self.activity_execution_service.save_activity_execution(
            activity_execution)

        if previous_id in [
                relation['start_node'] for relation in relationships
                if relation['name'] == 'hasScenario'
        ]:
            self.graph_api_service.create_relationships(
                previous_id, activity_execution_result.id, 'hasScenario')
        else:
            self.graph_api_service.create_relationships(
                previous_id, activity_execution_result.id,
                'nextActivityExecution')

        try:
            next_id, relation_id = [
                (relation['end_node'], relation['id'])
                for relation in relationships if
                relation['name'] in ['hasScenario', 'nextActivityExecution']
                and relation['start_node'] == previous_id
            ][0]
        except IndexError:
            return activity_execution_result

        self.graph_api_service.create_relationships(
            activity_execution_result.id, next_id, 'nextActivityExecution')
        self.graph_api_service.delete_relationship(relation_id)

        return activity_execution_result

    def change_order_middle_with_last(self, middle_id, last_id,
                                      middle_relationships,
                                      last_relationships):
        """
            Changes order of the middle node and the last node

            Args:
                middle_id (int): Id of the middle node
                last_id (ActivityExecutionIn): Id of the last node
                middle_relationships: Relationships of the middle node
                last_relationships: Relationships of the last node
            Returns:
        """
        start_node, end_node, relation_name = (
            middle_relationships[0]['start_node'], last_id,
            middle_relationships[0]['name'])
        self.graph_api_service.create_relationships(start_node, end_node,
                                                    relation_name)

        if middle_relationships[1] == last_relationships[0]:
            # nodes are next to each other
            start_node, end_node, relation_name = (
                last_id, middle_relationships[1]['start_node'],
                middle_relationships[1]['name'])
            self.graph_api_service.create_relationships(
                start_node, end_node, relation_name)

            return

        # nodes are separated by other nodes
        start_node, end_node, relation_name = (
            last_id, middle_relationships[1]['end_node'],
            middle_relationships[1]['name'])
        self.graph_api_service.create_relationships(start_node, end_node,
                                                    relation_name)

        start_node, end_node, relation_name = (
            last_relationships[0]['start_node'], middle_id,
            last_relationships[0]['name'])
        if start_node != end_node:
            self.graph_api_service.create_relationships(
                start_node, end_node, relation_name)

        return

    def change_order_middle_with_middle(self, middle_id, last_id,
                                        middle_relationships,
                                        last_relationships):
        """
            Changes order of the two middle nodes

            Args:
                middle_id (int): Id of the middle node
                last_id (ActivityExecutionIn): Id of the middle node but second in order
                middle_relationships: Relationships of the middle node
                last_relationships: Relationships of the last node
            Returns:
        """
        start_node, end_node, relation_name = (
            middle_relationships[0]['start_node'], last_id,
            middle_relationships[0]['name'])
        self.graph_api_service.create_relationships(start_node, end_node,
                                                    relation_name)

        if middle_relationships[1] == last_relationships[0]:
            # nodes are next to each other
            start_node, end_node, relation_name = (
                last_id, middle_id, middle_relationships[1]['name'])
            self.graph_api_service.create_relationships(
                start_node, end_node, relation_name)

            start_node, end_node, relation_name = (
                middle_id, last_relationships[1]['end_node'],
                last_relationships[1]['name'])
            self.graph_api_service.create_relationships(
                start_node, end_node, relation_name)

            return

        start_node, end_node, relation_name = (
            last_id, middle_relationships[1]['end_node'],
            middle_relationships[1]['name'])
        self.graph_api_service.create_relationships(start_node, end_node,
                                                    relation_name)

        start_node, end_node, relation_name = (
            last_relationships[0]['start_node'], middle_id,
            last_relationships[0]['name'])
        if start_node != end_node:
            self.graph_api_service.create_relationships(
                start_node, end_node, relation_name)

        start_node, end_node, relation_name = (
            middle_id, last_relationships[1]['end_node'],
            last_relationships[1]['name'])
        self.graph_api_service.create_relationships(start_node, end_node,
                                                    relation_name)

        return

    def what_order(self, previous_relationships,
                   activity_execution_relationships):
        """
            Finds which node is in which order (starting from experiment) in the scenario

            Args:
                previous_relationships: Relationships of the previous node
                activity_execution_relationships: Relationships of the activity execution node
            Returns:
                True when is the first in order
                False when is the second in order
        """
        p_relationships = previous_relationships
        p_count = False
        a_relationships = activity_execution_relationships
        a_count = False
        while True:
            if p_relationships[0]['name'] is not None and p_relationships[0][
                    'name'] in ['hasScenario']:
                p_count = True
                break
            if a_relationships[0]['name'] is not None and a_relationships[0][
                    'name'] in ['hasScenario']:
                a_count = True
                break
            p_id = p_relationships[0]['start_node']
            p_relationships = self.graph_api_service.get_node_relationships(
                p_id)['relationships']
            p_relationships = [
                relation for relation in p_relationships if
                relation['name'] in ['nextActivityExecution', 'hasScenario']
                and relation['end_node'] == p_id
            ]
            a_id = a_relationships[0]['start_node']
            a_relationships = self.graph_api_service.get_node_relationships(
                a_id)['relationships']
            a_relationships = [
                relation for relation in a_relationships if
                relation['name'] in ['nextActivityExecution', 'hasScenario']
                and relation['end_node'] == a_id
            ]

        return p_count, a_count

    def swap_order_in_relationships_array(self, relationships, node_id):
        """
            Swaps order of relationships list so they are saved in order starting from experiment
            Args:
                relationships: List of relationships
                node_id: Id of node, that relationships belong to
            Returns:
                relationships: List of relationships in specified order
        """
        if relationships[0]['start_node'] == node_id:
            relationships[0], relationships[1] = relationships[
                1], relationships[0]

        return relationships

    def change_order(self, order_change: OrderChangeIn):
        """
        Send request to graph api to change order in scenario

        Args:
            order_change (OrderChangeIn): Ids of activity_executions to change order by

        Returns:
            Result of request as changed order ids
        """
        # save all relationships in lists
        previous_relationships = self.graph_api_service.get_node_relationships(
            order_change.previous_id)['relationships']
        activity_execution_relationships = \
            self.graph_api_service.get_node_relationships(order_change.activity_execution_id)['relationships']

        # check which node is before the other
        previous_first, activity_execution_first = self.what_order(
            previous_relationships, activity_execution_relationships)

        # delete nextActivityExecution and hasScenario relationships
        [
            self.graph_api_service.delete_relationship(relation['id'])
            for relation in previous_relationships
            if relation['name'] in ['nextActivityExecution', 'hasScenario']
        ]
        [
            self.graph_api_service.delete_relationship(relation['id'])
            for relation in activity_execution_relationships
            if relation['name'] in ['nextActivityExecution', 'hasScenario']
        ]

        # save nextActivityExecution and hasScenario relationships
        previous_relationships = [
            relation for relation in previous_relationships
            if relation['name'] in ['nextActivityExecution', 'hasScenario']
        ]
        activity_execution_relationships = [
            relation for relation in activity_execution_relationships
            if relation['name'] in ['nextActivityExecution', 'hasScenario']
        ]

        # swap order of relationships if needed
        previous_relationships = self.swap_order_in_relationships_array(
            previous_relationships, order_change.previous_id)
        activity_execution_relationships = self.swap_order_in_relationships_array(
            activity_execution_relationships,
            order_change.activity_execution_id)

        if len(activity_execution_relationships) == 1:
            # change order when activity execution node is last
            self.change_order_middle_with_last(
                middle_id=order_change.previous_id,
                last_id=order_change.activity_execution_id,
                middle_relationships=previous_relationships,
                last_relationships=activity_execution_relationships)

        elif len(previous_relationships) == 1:
            # change order when previous node is last
            self.change_order_middle_with_last(
                middle_id=order_change.activity_execution_id,
                last_id=order_change.previous_id,
                middle_relationships=activity_execution_relationships,
                last_relationships=previous_relationships)

        elif len(previous_relationships) == 2 and len(
                activity_execution_relationships) == 2:
            if previous_first is True:
                # change order when previous node is before activity execution node
                self.change_order_middle_with_middle(
                    middle_id=order_change.previous_id,
                    last_id=order_change.activity_execution_id,
                    middle_relationships=previous_relationships,
                    last_relationships=activity_execution_relationships)
            elif activity_execution_first is True:
                # change order when activity execution node is before previous node
                self.change_order_middle_with_middle(
                    middle_id=order_change.activity_execution_id,
                    last_id=order_change.previous_id,
                    middle_relationships=activity_execution_relationships,
                    last_relationships=previous_relationships)

        return OrderChangeOut(
            previous_id=order_change.previous_id,
            activity_execution_id=order_change.activity_execution_id)

    def delete_activity_execution(self, activity_execution_id: int):
        """
        Send request to graph api to delete activity_execution from scenario

        Args:
            activity_execution_id (int): Id of activity_execution to delete

        Returns:
            Result of request as activity_execution object
        """
        relationships = self.graph_api_service.get_node_relationships(
            activity_execution_id)['relationships']
        if len(relationships) == 0:
            return ActivityExecutionOut(errors='Relationships not found')

        activity_execution = self.graph_api_service.delete_node(
            activity_execution_id)

        properties = {
            p['key']: p['value']
            for p in activity_execution['properties']
        }
        additional_properties = [
            PropertyIn(key=key, value=value)
            for key, value in properties.items() if key not in ['activity']
        ]
        activity_execution_response = ActivityExecutionOut(
            id=activity_execution['id'],
            activity=properties['activity'],
            additional_properties=additional_properties)

        if len([
                relationship for relationship in relationships
                if relationship['name'] in
            ['hasScenario', 'nextActivityExecution']
        ]) == 1:
            return activity_execution_response

        start_node = [
            relationship['start_node'] for relationship in relationships
            if relationship['end_node'] == activity_execution_id and
            relationship['name'] in ['hasScenario', 'nextActivityExecution']
        ][0]
        end_node = [
            relationship['end_node'] for relationship in relationships
            if relationship['start_node'] == activity_execution_id
            and relationship['name'] == 'nextActivityExecution'
        ][0]
        self.graph_api_service.create_relationships(start_node, end_node,
                                                    relationships[0]['name'])

        return activity_execution_response

    def get_scenario(self, node_id: int):
        """
        Send request to graph api to get activity executions and experiment from scenario

        Args:
            node_id (int): Id of experiment or activity execution which is included in scenario

        Returns:
            Result of request as Scenario object
        """
        get_response = self.graph_api_service.get_node(node_id)

        if get_response["errors"] is not None:
            return NotFoundByIdModel(id=node_id, errors=get_response["errors"])
        if get_response["labels"][0] not in [
                "Activity Execution", "Experiment"
        ]:
            return NotFoundByIdModel(id=node_id, errors="Node not found.")

        if get_response["labels"][0] == "Activity Execution":
            return self.get_scenario_by_activity_execution(node_id)
        elif get_response["labels"][0] == "Experiment":
            return self.get_scenario_by_experiment(node_id)

    def get_scenario_by_experiment(self, experiment_id: int):
        """
        Send request to graph api to get activity_executions from scenario which starts in experiment

        Args:
            experiment_id (int): Id of experiment where scenario starts

        Returns:
            Result of request as Scenario object
        """
        activity_executions = []

        experiment = self.experiment_service.get_experiment(experiment_id)
        if type(experiment) is NotFoundByIdModel:
            return experiment

        scenario = {'experiment_id': experiment_id, 'activity_executions': []}

        for relation in experiment.relations:
            if relation.name == "hasScenario":
                self.get_scenario_after_activity_execution(
                    relation.second_node_id, activity_executions)

        [
            scenario['activity_executions'].append(a)
            for a in activity_executions
            if a not in scenario['activity_executions']
        ]

        return ScenarioOut(**scenario)

    def get_scenario_by_activity_execution(self, activity_execution_id: int):
        """
        Send request to graph api to get activity_executions from scenario which has activity execution id incuded

        Args:
            activity_execution_id (int): Id of activity execution included in scenario

        Returns:
            Result of request as Scenario object
        """
        activity_executions = []
        activity_executions_before = []

        activity_execution = self.activity_execution_service.get_activity_execution(
            activity_execution_id)
        if type(activity_execution) is NotFoundByIdModel:
            return activity_execution

        experiment_id = self.get_scenario_before_activity_execution(
            activity_execution_id, activity_executions_before)
        [
            activity_executions.append(a) for a in activity_executions_before
            if a not in activity_executions
        ]

        activity_executions_after = []

        self.get_scenario_after_activity_execution(activity_execution_id,
                                                   activity_executions_after)
        [
            activity_executions.append(a) for a in activity_executions_after
            if a not in activity_executions
        ]

        scenario = {'experiment_id': experiment_id, 'activity_executions': []}

        [
            scenario['activity_executions'].append(a)
            for a in activity_executions
        ]

        return ScenarioOut(**scenario)

    def get_scenario_after_activity_execution(self, activity_execution_id: int,
                                              activity_executions: []):
        """
        Gets activity executions from scenario which are saved after activity_execution_id

        Args:
            activity_execution_id (int): Id of activity execution included in scenario
            activity_executions: List of activity executions in scenario
        """
        activity_execution = self.activity_execution_service.get_activity_execution(
            activity_execution_id)

        if type(activity_execution) is NotFoundByIdModel:
            return activity_execution

        activity_executions.append(activity_execution)

        for relation in activity_execution.relations:
            if relation.name == "nextActivityExecution":
                activity_execution = self.activity_execution_service.get_activity_execution(
                    relation.second_node_id)

                if type(activity_execution) is NotFoundByIdModel:
                    return activity_execution

                activity_executions.append(activity_execution)
                [
                    self.get_scenario_after_activity_execution(
                        activity_execution.id, activity_executions)
                    for r in activity_execution.relations
                    if r.name == "nextActivityExecution"
                ]

    def get_scenario_before_activity_execution(self,
                                               activity_execution_id: int,
                                               activity_executions: []):
        """
        Gets activity executions from scenario which are saved before activity_execution_id

        Args:
            activity_execution_id (int): Id of activity execution included in scenario
            activity_executions: List of activity executions in scenario
        """
        activity_execution = self.activity_execution_service.get_activity_execution(
            activity_execution_id)
        if type(activity_execution) is NotFoundByIdModel:
            return activity_execution

        activity_executions.append(activity_execution)

        for relation in activity_execution.reversed_relations:

            if relation.name == "nextActivityExecution":
                activity_execution = self.activity_execution_service.get_activity_execution(
                    relation.second_node_id)
                if type(activity_execution) is NotFoundByIdModel:
                    return activity_execution

                activity_executions.append(activity_execution)
                return self.get_scenario_before_activity_execution(
                    activity_execution.id, activity_executions)

            elif relation.name == "hasScenario":
                experiment = self.experiment_service.get_experiment(
                    relation.second_node_id)
                if type(experiment) is NotFoundByIdModel:
                    return experiment

                return experiment.id