mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-25 20:40:36 +03:00
R: Restructuring layout data
This commit is contained in:
parent
5584fa9fc8
commit
8aedd4c209
|
@ -9,6 +9,7 @@ from apps.library.models import (
|
|||
LibraryTemplate,
|
||||
LocationHead
|
||||
)
|
||||
from apps.oss.models import OperationSchema
|
||||
from apps.rsform.models import RSForm
|
||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||
from shared.testing_utils import response_contains
|
||||
|
@ -58,6 +59,8 @@ class TestLibraryViewset(EndpointTester):
|
|||
'read_only': True
|
||||
}
|
||||
response = self.executeCreated(data=data)
|
||||
oss = OperationSchema(LibraryItem.objects.get(pk=response.data['id']))
|
||||
self.assertEqual(oss.model.owner, self.user)
|
||||
self.assertEqual(response.data['owner'], self.user.pk)
|
||||
self.assertEqual(response.data['item_type'], data['item_type'])
|
||||
self.assertEqual(response.data['title'], data['title'])
|
||||
|
@ -65,6 +68,8 @@ class TestLibraryViewset(EndpointTester):
|
|||
self.assertEqual(response.data['access_policy'], data['access_policy'])
|
||||
self.assertEqual(response.data['visible'], data['visible'])
|
||||
self.assertEqual(response.data['read_only'], data['read_only'])
|
||||
self.assertEqual(oss.layout().data['operations'], [])
|
||||
self.assertEqual(oss.layout().data['blocks'], [])
|
||||
|
||||
self.logout()
|
||||
data = {'title': 'Title2'}
|
||||
|
|
|
@ -13,7 +13,7 @@ from rest_framework.decorators import action
|
|||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
|
||||
from apps.oss.models import Operation, OperationSchema, PropagationFacade
|
||||
from apps.oss.models import Layout, Operation, OperationSchema, PropagationFacade
|
||||
from apps.rsform.models import RSForm
|
||||
from apps.rsform.serializers import RSFormParseSerializer
|
||||
from apps.users.models import User
|
||||
|
@ -40,6 +40,8 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
serializer.save(owner=self.request.user)
|
||||
else:
|
||||
serializer.save()
|
||||
if serializer.data.get('item_type') == m.LibraryItemType.OPERATION_SCHEMA:
|
||||
Layout.objects.create(oss=serializer.instance, data={'operations': [], 'blocks': []})
|
||||
|
||||
def perform_update(self, serializer) -> None:
|
||||
instance = serializer.save()
|
||||
|
|
|
@ -15,11 +15,24 @@ class OperationAdmin(admin.ModelAdmin):
|
|||
'alias',
|
||||
'title',
|
||||
'description',
|
||||
'position_x',
|
||||
'position_y']
|
||||
'parent']
|
||||
search_fields = ['id', 'operation_type', 'title', 'alias']
|
||||
|
||||
|
||||
class BlockAdmin(admin.ModelAdmin):
|
||||
''' Admin model: Block. '''
|
||||
ordering = ['oss']
|
||||
list_display = ['id', 'oss', 'title', 'description', 'parent']
|
||||
search_fields = ['oss']
|
||||
|
||||
|
||||
class LayoutAdmin(admin.ModelAdmin):
|
||||
''' Admin model: Layout. '''
|
||||
ordering = ['oss']
|
||||
list_display = ['id', 'oss', 'data']
|
||||
search_fields = ['oss']
|
||||
|
||||
|
||||
class ArgumentAdmin(admin.ModelAdmin):
|
||||
''' Admin model: Operation arguments. '''
|
||||
ordering = ['operation']
|
||||
|
@ -42,6 +55,8 @@ class InheritanceAdmin(admin.ModelAdmin):
|
|||
|
||||
|
||||
admin.site.register(models.Operation, OperationAdmin)
|
||||
admin.site.register(models.Block, BlockAdmin)
|
||||
admin.site.register(models.Layout, LayoutAdmin)
|
||||
admin.site.register(models.Argument, ArgumentAdmin)
|
||||
admin.site.register(models.Substitution, SynthesisSubstitutionAdmin)
|
||||
admin.site.register(models.Inheritance, InheritanceAdmin)
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
# Generated by Django 5.1.7 on 2025-03-26 16:04
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate_layout(apps, schema_editor):
|
||||
LibraryItem = apps.get_model('library', 'LibraryItem')
|
||||
Operation = apps.get_model('oss', 'Operation')
|
||||
Layout = apps.get_model('oss', 'Layout')
|
||||
|
||||
for library_item in LibraryItem.objects.filter(item_type='oss'):
|
||||
layout_data = {'operations': [], 'blocks': []}
|
||||
|
||||
operations = Operation.objects.filter(oss=library_item)
|
||||
for operation in operations:
|
||||
layout_data['operations'].append({
|
||||
'id': operation.id,
|
||||
'x': operation.position_x,
|
||||
'y': operation.position_y
|
||||
})
|
||||
|
||||
Layout.objects.create(oss=library_item, data=layout_data)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0007_rename_libraryitem_comment_libraryitem_description'),
|
||||
('oss', '0010_rename_comment_operation_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Layout',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('data', models.JSONField(default=dict, verbose_name='Расположение')),
|
||||
('oss', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='layout', to='library.libraryitem', verbose_name='Схема синтеза')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Схема расположения',
|
||||
'verbose_name_plural': 'Схемы расположения',
|
||||
},
|
||||
),
|
||||
migrations.RunPython(migrate_layout),
|
||||
migrations.RemoveField(
|
||||
model_name='operation',
|
||||
name='position_x',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='operation',
|
||||
name='position_y',
|
||||
),
|
||||
|
||||
migrations.CreateModel(
|
||||
name='Block',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.TextField(blank=True, verbose_name='Название')),
|
||||
('description', models.TextField(blank=True, verbose_name='Описание')),
|
||||
('oss', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='blocks', to='library.libraryitem', verbose_name='Схема синтеза')),
|
||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='as_child_block', to='oss.block', verbose_name='Содержащий блок')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Блок',
|
||||
'verbose_name_plural': 'Блоки',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='operation',
|
||||
name='parent',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='as_child_operation',
|
||||
to='oss.block',
|
||||
verbose_name='Содержащий блок'),
|
||||
),
|
||||
]
|
39
rsconcept/backend/apps/oss/models/Block.py
Normal file
39
rsconcept/backend/apps/oss/models/Block.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
''' Models: Content Block in OSS. '''
|
||||
# pylint: disable=duplicate-code
|
||||
from django.db.models import CASCADE, SET_NULL, ForeignKey, Model, TextField
|
||||
|
||||
|
||||
class Block(Model):
|
||||
''' Block of content in OSS.'''
|
||||
oss = ForeignKey(
|
||||
verbose_name='Схема синтеза',
|
||||
to='library.LibraryItem',
|
||||
on_delete=CASCADE,
|
||||
related_name='blocks'
|
||||
)
|
||||
|
||||
title = TextField(
|
||||
verbose_name='Название',
|
||||
blank=True
|
||||
)
|
||||
description = TextField(
|
||||
verbose_name='Описание',
|
||||
blank=True
|
||||
)
|
||||
|
||||
parent = ForeignKey(
|
||||
verbose_name='Содержащий блок',
|
||||
to='oss.Block',
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=SET_NULL,
|
||||
related_name='as_child_block'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
verbose_name = 'Блок'
|
||||
verbose_name_plural = 'Блоки'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'Блок {self.title}'
|
25
rsconcept/backend/apps/oss/models/Layout.py
Normal file
25
rsconcept/backend/apps/oss/models/Layout.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
''' Models: Content Block in OSS. '''
|
||||
from django.db.models import CASCADE, ForeignKey, JSONField, Model
|
||||
|
||||
|
||||
class Layout(Model):
|
||||
''' Node layout in OSS.'''
|
||||
oss = ForeignKey(
|
||||
verbose_name='Схема синтеза',
|
||||
to='library.LibraryItem',
|
||||
on_delete=CASCADE,
|
||||
related_name='layout'
|
||||
)
|
||||
|
||||
data = JSONField(
|
||||
verbose_name='Расположение',
|
||||
default=dict
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
verbose_name = 'Схема расположения'
|
||||
verbose_name_plural = 'Схемы расположения'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'Схема расположения {self.oss.alias}'
|
|
@ -1,9 +1,9 @@
|
|||
''' Models: Operation in OSS. '''
|
||||
# pylint: disable=duplicate-code
|
||||
from django.db.models import (
|
||||
CASCADE,
|
||||
SET_NULL,
|
||||
CharField,
|
||||
FloatField,
|
||||
ForeignKey,
|
||||
Model,
|
||||
QuerySet,
|
||||
|
@ -44,6 +44,15 @@ class Operation(Model):
|
|||
related_name='producer'
|
||||
)
|
||||
|
||||
parent = ForeignKey(
|
||||
verbose_name='Содержащий блок',
|
||||
to='oss.Block',
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=SET_NULL,
|
||||
related_name='as_child_operation'
|
||||
)
|
||||
|
||||
alias = CharField(
|
||||
verbose_name='Шифр',
|
||||
max_length=255,
|
||||
|
@ -58,15 +67,6 @@ class Operation(Model):
|
|||
blank=True
|
||||
)
|
||||
|
||||
position_x = FloatField(
|
||||
verbose_name='Положение по горизонтали',
|
||||
default=0
|
||||
)
|
||||
position_y = FloatField(
|
||||
verbose_name='Положение по вертикали',
|
||||
default=0
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
verbose_name = 'Операция'
|
||||
|
|
|
@ -20,6 +20,7 @@ from apps.rsform.models import (
|
|||
|
||||
from .Argument import Argument
|
||||
from .Inheritance import Inheritance
|
||||
from .Layout import Layout
|
||||
from .Operation import Operation
|
||||
from .Substitution import Substitution
|
||||
|
||||
|
@ -38,6 +39,7 @@ class OperationSchema:
|
|||
def create(**kwargs) -> 'OperationSchema':
|
||||
''' Create LibraryItem via OperationSchema. '''
|
||||
model = LibraryItem.objects.create(item_type=LibraryItemType.OPERATION_SCHEMA, **kwargs)
|
||||
Layout.objects.create(oss=model, data={'operations': [], 'blocks': []})
|
||||
return OperationSchema(model)
|
||||
|
||||
@staticmethod
|
||||
|
@ -62,6 +64,12 @@ class OperationSchema:
|
|||
''' Operation arguments. '''
|
||||
return Argument.objects.filter(operation__oss=self.model)
|
||||
|
||||
def layout(self) -> Layout:
|
||||
''' OSS layout. '''
|
||||
result = Layout.objects.filter(oss=self.model).first()
|
||||
assert result is not None
|
||||
return result
|
||||
|
||||
def substitutions(self) -> QuerySet[Substitution]:
|
||||
''' Operation substitutions. '''
|
||||
return Substitution.objects.filter(operation__oss=self.model)
|
||||
|
@ -78,15 +86,11 @@ class OperationSchema:
|
|||
location=self.model.location
|
||||
)
|
||||
|
||||
def update_positions(self, data: list[dict]) -> None:
|
||||
def update_layout(self, data: dict) -> None:
|
||||
''' Update positions. '''
|
||||
lookup = {x['id']: x for x in data}
|
||||
operations = self.operations()
|
||||
for item in operations:
|
||||
if item.pk in lookup:
|
||||
item.position_x = lookup[item.pk]['position_x']
|
||||
item.position_y = lookup[item.pk]['position_y']
|
||||
Operation.objects.bulk_update(operations, ['position_x', 'position_y'])
|
||||
layout = self.layout()
|
||||
layout.data = data
|
||||
layout.save()
|
||||
|
||||
def create_operation(self, **kwargs) -> Operation:
|
||||
''' Insert new operation. '''
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
''' Django: Models. '''
|
||||
|
||||
from .Argument import Argument
|
||||
from .Block import Block
|
||||
from .Inheritance import Inheritance
|
||||
from .Layout import Layout
|
||||
from .Operation import Operation, OperationType
|
||||
from .OperationSchema import OperationSchema
|
||||
from .PropagationFacade import PropagationFacade
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
''' REST API: Serializers. '''
|
||||
|
||||
from .basics import OperationPositionSerializer, PositionsSerializer, SubstitutionExSerializer
|
||||
from .basics import LayoutSerializer, SubstitutionExSerializer
|
||||
from .data_access import (
|
||||
ArgumentSerializer,
|
||||
OperationCreateSerializer,
|
||||
|
|
|
@ -2,17 +2,29 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
|
||||
class OperationPositionSerializer(serializers.Serializer):
|
||||
class OperationNodeSerializer(serializers.Serializer):
|
||||
''' Operation position. '''
|
||||
id = serializers.IntegerField()
|
||||
position_x = serializers.FloatField()
|
||||
position_y = serializers.FloatField()
|
||||
x = serializers.FloatField()
|
||||
y = serializers.FloatField()
|
||||
|
||||
|
||||
class PositionsSerializer(serializers.Serializer):
|
||||
''' Operations position for OperationSchema. '''
|
||||
positions = serializers.ListField(
|
||||
child=OperationPositionSerializer()
|
||||
class BlockNodeSerializer(serializers.Serializer):
|
||||
''' Block position. '''
|
||||
id = serializers.IntegerField()
|
||||
x = serializers.FloatField()
|
||||
y = serializers.FloatField()
|
||||
width = serializers.FloatField()
|
||||
height = serializers.FloatField()
|
||||
|
||||
|
||||
class LayoutSerializer(serializers.Serializer):
|
||||
''' Layout for OperationSchema. '''
|
||||
blocks = serializers.ListField(
|
||||
child=BlockNodeSerializer()
|
||||
)
|
||||
operations = serializers.ListField(
|
||||
child=OperationNodeSerializer()
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ from apps.rsform.serializers import SubstitutionSerializerBase
|
|||
from shared import messages as msg
|
||||
|
||||
from ..models import Argument, Inheritance, Operation, OperationSchema, OperationType
|
||||
from .basics import OperationPositionSerializer, SubstitutionExSerializer
|
||||
from .basics import LayoutSerializer, SubstitutionExSerializer
|
||||
|
||||
|
||||
class OperationSerializer(serializers.ModelSerializer):
|
||||
|
@ -44,17 +44,16 @@ class OperationCreateSerializer(serializers.Serializer):
|
|||
model = Operation
|
||||
fields = \
|
||||
'alias', 'operation_type', 'title', \
|
||||
'description', 'result', 'position_x', 'position_y'
|
||||
'description', 'result', 'parent'
|
||||
|
||||
layout = LayoutSerializer()
|
||||
position_x = serializers.FloatField()
|
||||
position_y = serializers.FloatField()
|
||||
|
||||
create_schema = serializers.BooleanField(default=False, required=False)
|
||||
item_data = OperationCreateData()
|
||||
create_schema = serializers.BooleanField(default=False, required=False)
|
||||
arguments = PKField(many=True, queryset=Operation.objects.all().only('pk'), required=False)
|
||||
|
||||
positions = serializers.ListField(
|
||||
child=OperationPositionSerializer(),
|
||||
default=[]
|
||||
)
|
||||
|
||||
|
||||
class OperationUpdateSerializer(serializers.Serializer):
|
||||
''' Serializer: Operation update. '''
|
||||
|
@ -65,6 +64,7 @@ class OperationUpdateSerializer(serializers.Serializer):
|
|||
model = Operation
|
||||
fields = 'alias', 'title', 'description'
|
||||
|
||||
layout = LayoutSerializer()
|
||||
target = PKField(many=False, queryset=Operation.objects.all())
|
||||
item_data = OperationUpdateData()
|
||||
arguments = PKField(many=True, queryset=Operation.objects.all().only('oss_id', 'result_id'), required=False)
|
||||
|
@ -73,11 +73,6 @@ class OperationUpdateSerializer(serializers.Serializer):
|
|||
required=False
|
||||
)
|
||||
|
||||
positions = serializers.ListField(
|
||||
child=OperationPositionSerializer(),
|
||||
default=[]
|
||||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
if 'arguments' not in attrs:
|
||||
return attrs
|
||||
|
@ -120,11 +115,8 @@ class OperationUpdateSerializer(serializers.Serializer):
|
|||
|
||||
class OperationTargetSerializer(serializers.Serializer):
|
||||
''' Serializer: Target single operation. '''
|
||||
layout = LayoutSerializer()
|
||||
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result_id'))
|
||||
positions = serializers.ListField(
|
||||
child=OperationPositionSerializer(),
|
||||
default=[]
|
||||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
oss = cast(LibraryItem, self.context['oss'])
|
||||
|
@ -138,11 +130,8 @@ class OperationTargetSerializer(serializers.Serializer):
|
|||
|
||||
class OperationDeleteSerializer(serializers.Serializer):
|
||||
''' Serializer: Delete operation. '''
|
||||
layout = LayoutSerializer()
|
||||
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result'))
|
||||
positions = serializers.ListField(
|
||||
child=OperationPositionSerializer(),
|
||||
default=[]
|
||||
)
|
||||
keep_constituents = serializers.BooleanField(default=False, required=False)
|
||||
delete_schema = serializers.BooleanField(default=False, required=False)
|
||||
|
||||
|
@ -158,6 +147,7 @@ class OperationDeleteSerializer(serializers.Serializer):
|
|||
|
||||
class SetOperationInputSerializer(serializers.Serializer):
|
||||
''' Serializer: Set input schema for operation. '''
|
||||
layout = LayoutSerializer()
|
||||
target = PKField(many=False, queryset=Operation.objects.all())
|
||||
input = PKField(
|
||||
many=False,
|
||||
|
@ -165,10 +155,6 @@ class SetOperationInputSerializer(serializers.Serializer):
|
|||
allow_null=True,
|
||||
default=None
|
||||
)
|
||||
positions = serializers.ListField(
|
||||
child=OperationPositionSerializer(),
|
||||
default=[]
|
||||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
oss = cast(LibraryItem, self.context['oss'])
|
||||
|
@ -186,7 +172,7 @@ class SetOperationInputSerializer(serializers.Serializer):
|
|||
|
||||
class OperationSchemaSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: Detailed data for OSS. '''
|
||||
items = serializers.ListField(
|
||||
operations = serializers.ListField(
|
||||
child=OperationSerializer()
|
||||
)
|
||||
arguments = serializers.ListField(
|
||||
|
@ -195,6 +181,7 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
|
|||
substitutions = serializers.ListField(
|
||||
child=SubstitutionExSerializer()
|
||||
)
|
||||
layout = LayoutSerializer()
|
||||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -205,9 +192,10 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
|
|||
result = LibraryItemDetailsSerializer(instance).data
|
||||
del result['versions']
|
||||
oss = OperationSchema(instance)
|
||||
result['items'] = []
|
||||
result['layout'] = oss.layout().data
|
||||
result['operations'] = []
|
||||
for operation in oss.operations().order_by('pk'):
|
||||
result['items'].append(OperationSerializer(operation).data)
|
||||
result['operations'].append(OperationSerializer(operation).data)
|
||||
result['arguments'] = []
|
||||
for argument in oss.arguments().order_by('order'):
|
||||
result['arguments'].append(ArgumentSerializer(argument).data)
|
||||
|
|
|
@ -25,9 +25,8 @@ class TestOperation(TestCase):
|
|||
def test_create_default(self):
|
||||
self.assertEqual(self.operation.oss, self.oss.model)
|
||||
self.assertEqual(self.operation.operation_type, OperationType.INPUT)
|
||||
self.assertEqual(self.operation.parent, None)
|
||||
self.assertEqual(self.operation.result, None)
|
||||
self.assertEqual(self.operation.alias, 'KS1')
|
||||
self.assertEqual(self.operation.title, '')
|
||||
self.assertEqual(self.operation.description, '')
|
||||
self.assertEqual(self.operation.position_x, 0)
|
||||
self.assertEqual(self.operation.position_y, 0)
|
||||
|
|
|
@ -58,6 +58,18 @@ class TestChangeAttributes(EndpointTester):
|
|||
self.operation3.refresh_from_db()
|
||||
self.ks3 = RSForm(self.operation3.result)
|
||||
|
||||
self.layout_data = {
|
||||
'operations': [
|
||||
{'id': self.operation1.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation2.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation3.pk, 'x': 0, 'y': 0},
|
||||
],
|
||||
'blocks': []
|
||||
}
|
||||
layout = self.owned.layout()
|
||||
layout.data = self.layout_data
|
||||
layout.save()
|
||||
|
||||
@decl_endpoint('/api/library/{item}/set-owner', method='patch')
|
||||
def test_set_owner(self):
|
||||
data = {'user': self.user3.pk}
|
||||
|
@ -142,7 +154,7 @@ class TestChangeAttributes(EndpointTester):
|
|||
'title': 'Test title mod',
|
||||
'description': 'Comment mod'
|
||||
},
|
||||
'positions': [],
|
||||
'layout': self.layout_data
|
||||
}
|
||||
|
||||
response = self.executeOK(data=data, item=self.owned_id)
|
||||
|
|
|
@ -57,6 +57,18 @@ class TestChangeConstituents(EndpointTester):
|
|||
self.ks3 = RSForm(self.operation3.result)
|
||||
self.assertEqual(self.ks3.constituents().count(), 4)
|
||||
|
||||
self.layout_data = {
|
||||
'operations': [
|
||||
{'id': self.operation1.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation2.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation3.pk, 'x': 0, 'y': 0},
|
||||
],
|
||||
'blocks': []
|
||||
}
|
||||
layout = self.owned.layout()
|
||||
layout.data = self.layout_data
|
||||
layout.save()
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/details', method='get')
|
||||
def test_retrieve_inheritance(self):
|
||||
response = self.executeOK(item=self.ks3.model.pk)
|
||||
|
|
|
@ -106,6 +106,20 @@ class TestChangeOperations(EndpointTester):
|
|||
convention='KS5D4'
|
||||
)
|
||||
|
||||
self.layout_data = {
|
||||
'operations': [
|
||||
{'id': self.operation1.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation2.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation3.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation4.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation5.pk, 'x': 0, 'y': 0},
|
||||
],
|
||||
'blocks': []
|
||||
}
|
||||
layout = self.owned.layout()
|
||||
layout.data = self.layout_data
|
||||
layout.save()
|
||||
|
||||
def test_oss_setup(self):
|
||||
self.assertEqual(self.ks1.constituents().count(), 3)
|
||||
self.assertEqual(self.ks2.constituents().count(), 3)
|
||||
|
@ -117,7 +131,7 @@ class TestChangeOperations(EndpointTester):
|
|||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||
def test_delete_input_operation(self):
|
||||
data = {
|
||||
'positions': [],
|
||||
'layout': self.layout_data,
|
||||
'target': self.operation2.pk
|
||||
}
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
|
@ -137,7 +151,7 @@ class TestChangeOperations(EndpointTester):
|
|||
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
|
||||
def test_set_input_null(self):
|
||||
data = {
|
||||
'positions': [],
|
||||
'layout': self.layout_data,
|
||||
'target': self.operation2.pk,
|
||||
'input': None
|
||||
}
|
||||
|
@ -169,7 +183,7 @@ class TestChangeOperations(EndpointTester):
|
|||
ks6D1 = ks6.insert_new('D1', definition_formal='X1 X2', convention='KS6D1')
|
||||
|
||||
data = {
|
||||
'positions': [],
|
||||
'layout': self.layout_data,
|
||||
'target': self.operation2.pk,
|
||||
'input': ks6.model.pk
|
||||
}
|
||||
|
@ -211,7 +225,7 @@ class TestChangeOperations(EndpointTester):
|
|||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||
def test_delete_operation_and_constituents(self):
|
||||
data = {
|
||||
'positions': [],
|
||||
'layout': self.layout_data,
|
||||
'target': self.operation1.pk,
|
||||
'keep_constituents': False,
|
||||
'delete_schema': True
|
||||
|
@ -232,7 +246,7 @@ class TestChangeOperations(EndpointTester):
|
|||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||
def test_delete_operation_keep_constituents(self):
|
||||
data = {
|
||||
'positions': [],
|
||||
'layout': self.layout_data,
|
||||
'target': self.operation1.pk,
|
||||
'keep_constituents': True,
|
||||
'delete_schema': True
|
||||
|
@ -253,7 +267,7 @@ class TestChangeOperations(EndpointTester):
|
|||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||
def test_delete_operation_keep_schema(self):
|
||||
data = {
|
||||
'positions': [],
|
||||
'layout': self.layout_data,
|
||||
'target': self.operation1.pk,
|
||||
'keep_constituents': True,
|
||||
'delete_schema': False
|
||||
|
@ -283,7 +297,7 @@ class TestChangeOperations(EndpointTester):
|
|||
'title': 'Test title mod',
|
||||
'description': 'Comment mod'
|
||||
},
|
||||
'positions': [],
|
||||
'layout': self.layout_data,
|
||||
'substitutions': [
|
||||
{
|
||||
'original': self.ks1X1.pk,
|
||||
|
@ -317,7 +331,7 @@ class TestChangeOperations(EndpointTester):
|
|||
'title': 'Test title mod',
|
||||
'description': 'Comment mod'
|
||||
},
|
||||
'positions': [],
|
||||
'layout': self.layout_data,
|
||||
'arguments': [self.operation1.pk],
|
||||
}
|
||||
|
||||
|
@ -356,7 +370,7 @@ class TestChangeOperations(EndpointTester):
|
|||
|
||||
data = {
|
||||
'target': self.operation4.pk,
|
||||
'positions': []
|
||||
'layout': self.layout_data
|
||||
}
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.operation4.refresh_from_db()
|
||||
|
|
|
@ -106,6 +106,20 @@ class TestChangeSubstitutions(EndpointTester):
|
|||
convention='KS5D4'
|
||||
)
|
||||
|
||||
self.layout_data = {
|
||||
'operations': [
|
||||
{'id': self.operation1.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation2.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation3.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation4.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation5.pk, 'x': 0, 'y': 0},
|
||||
],
|
||||
'blocks': []
|
||||
}
|
||||
layout = self.owned.layout()
|
||||
layout.data = self.layout_data
|
||||
layout.save()
|
||||
|
||||
|
||||
def test_oss_setup(self):
|
||||
self.assertEqual(self.ks1.constituents().count(), 3)
|
||||
|
@ -139,10 +153,12 @@ class TestChangeSubstitutions(EndpointTester):
|
|||
|
||||
@decl_endpoint('/api/rsforms/{schema}/substitute', method='patch')
|
||||
def test_substitute_substitution(self):
|
||||
data = {'substitutions': [{
|
||||
'original': self.ks2S1.pk,
|
||||
'substitution': self.ks2X1.pk
|
||||
}]}
|
||||
data = {
|
||||
'substitutions': [{
|
||||
'original': self.ks2S1.pk,
|
||||
'substitution': self.ks2X1.pk
|
||||
}]
|
||||
}
|
||||
self.executeOK(data=data, schema=self.ks2.model.pk)
|
||||
self.ks4D1.refresh_from_db()
|
||||
self.ks4D2.refresh_from_db()
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
''' Tests for REST API. '''
|
||||
from .t_operations import *
|
||||
from .t_oss import *
|
||||
|
|
447
rsconcept/backend/apps/oss/tests/s_views/t_operations.py
Normal file
447
rsconcept/backend/apps/oss/tests/s_views/t_operations.py
Normal file
|
@ -0,0 +1,447 @@
|
|||
''' Testing API: Operation Schema. '''
|
||||
from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType
|
||||
from apps.oss.models import Operation, OperationSchema, OperationType
|
||||
from apps.rsform.models import Constituenta, RSForm
|
||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||
|
||||
|
||||
class TestOssOperations(EndpointTester):
|
||||
''' Testing OSS view - operations. '''
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.owned = OperationSchema.create(title='Test', alias='T1', owner=self.user)
|
||||
self.owned_id = self.owned.model.pk
|
||||
self.unowned = OperationSchema.create(title='Test2', alias='T2')
|
||||
self.unowned_id = self.unowned.model.pk
|
||||
self.private = OperationSchema.create(title='Test2', alias='T2', access_policy=AccessPolicy.PRIVATE)
|
||||
self.private_id = self.private.model.pk
|
||||
self.invalid_id = self.private.model.pk + 1337
|
||||
|
||||
def populateData(self):
|
||||
self.ks1 = RSForm.create(
|
||||
alias='KS1',
|
||||
title='Test1',
|
||||
owner=self.user
|
||||
)
|
||||
self.ks1X1 = self.ks1.insert_new(
|
||||
'X1',
|
||||
term_raw='X1_1',
|
||||
term_resolved='X1_1'
|
||||
)
|
||||
self.ks2 = RSForm.create(
|
||||
alias='KS2',
|
||||
title='Test2',
|
||||
owner=self.user
|
||||
)
|
||||
self.ks2X1 = self.ks2.insert_new(
|
||||
'X2',
|
||||
term_raw='X1_2',
|
||||
term_resolved='X1_2'
|
||||
)
|
||||
|
||||
self.operation1 = self.owned.create_operation(
|
||||
alias='1',
|
||||
operation_type=OperationType.INPUT,
|
||||
result=self.ks1.model
|
||||
)
|
||||
self.operation2 = self.owned.create_operation(
|
||||
alias='2',
|
||||
operation_type=OperationType.INPUT,
|
||||
result=self.ks2.model
|
||||
)
|
||||
self.operation3 = self.owned.create_operation(
|
||||
alias='3',
|
||||
operation_type=OperationType.SYNTHESIS
|
||||
)
|
||||
self.layout_data = {
|
||||
'operations': [
|
||||
{'id': self.operation1.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation2.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation3.pk, 'x': 0, 'y': 0},
|
||||
],
|
||||
'blocks': []
|
||||
}
|
||||
layout = self.owned.layout()
|
||||
layout.data = self.layout_data
|
||||
layout.save()
|
||||
|
||||
self.owned.set_arguments(self.operation3.pk, [self.operation1, self.operation2])
|
||||
self.owned.set_substitutions(self.operation3.pk, [{
|
||||
'original': self.ks1X1,
|
||||
'substitution': self.ks2X1
|
||||
}])
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||
def test_create_operation(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {
|
||||
'item_data': {
|
||||
'alias': 'Test3',
|
||||
'title': 'Test title',
|
||||
'description': 'Тест кириллицы',
|
||||
|
||||
},
|
||||
'layout': self.layout_data,
|
||||
'position_x': 1,
|
||||
'position_y': 1
|
||||
|
||||
}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['item_data']['operation_type'] = 'invalid'
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['item_data']['operation_type'] = OperationType.INPUT
|
||||
self.executeNotFound(data=data, item=self.invalid_id)
|
||||
|
||||
response = self.executeCreated(data=data, item=self.owned_id)
|
||||
self.assertEqual(len(response.data['oss']['operations']), 4)
|
||||
new_operation = response.data['new_operation']
|
||||
layout = response.data['oss']['layout']
|
||||
item = [item for item in layout['operations'] if item['id'] == new_operation['id']][0]
|
||||
self.assertEqual(new_operation['alias'], data['item_data']['alias'])
|
||||
self.assertEqual(new_operation['operation_type'], data['item_data']['operation_type'])
|
||||
self.assertEqual(new_operation['title'], data['item_data']['title'])
|
||||
self.assertEqual(new_operation['description'], data['item_data']['description'])
|
||||
self.assertEqual(new_operation['result'], None)
|
||||
self.assertEqual(new_operation['parent'], None)
|
||||
self.assertEqual(item['x'], data['position_x'])
|
||||
self.assertEqual(item['y'], data['position_y'])
|
||||
self.operation1.refresh_from_db()
|
||||
|
||||
self.executeForbidden(data=data, item=self.unowned_id)
|
||||
self.toggle_admin(True)
|
||||
self.executeCreated(data=data, item=self.unowned_id)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||
def test_create_operation_arguments(self):
|
||||
self.populateData()
|
||||
data = {
|
||||
'item_data': {
|
||||
'alias': 'Test4',
|
||||
'operation_type': OperationType.SYNTHESIS
|
||||
},
|
||||
'layout': self.layout_data,
|
||||
'position_x': 1,
|
||||
'position_y': 1,
|
||||
'arguments': [self.operation1.pk, self.operation3.pk]
|
||||
}
|
||||
response = self.executeCreated(data=data, item=self.owned_id)
|
||||
self.owned.refresh_from_db()
|
||||
new_operation = response.data['new_operation']
|
||||
arguments = self.owned.arguments()
|
||||
self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation1))
|
||||
self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation3))
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||
def test_create_operation_result(self):
|
||||
self.populateData()
|
||||
|
||||
self.operation1.result = None
|
||||
self.operation1.save()
|
||||
|
||||
data = {
|
||||
'item_data': {
|
||||
'alias': 'Test4',
|
||||
'operation_type': OperationType.INPUT,
|
||||
'result': self.ks1.model.pk
|
||||
},
|
||||
'layout': self.layout_data,
|
||||
'position_x': 1,
|
||||
'position_y': 1
|
||||
}
|
||||
response = self.executeCreated(data=data, item=self.owned_id)
|
||||
self.owned.refresh_from_db()
|
||||
new_operation = response.data['new_operation']
|
||||
self.assertEqual(new_operation['result'], self.ks1.model.pk)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||
def test_create_operation_schema(self):
|
||||
self.populateData()
|
||||
Editor.add(self.owned.model.pk, self.user2.pk)
|
||||
data = {
|
||||
'item_data': {
|
||||
'alias': 'Test4',
|
||||
'title': 'Test title',
|
||||
'description': 'Comment',
|
||||
'operation_type': OperationType.INPUT,
|
||||
'result': self.ks1.model.pk
|
||||
},
|
||||
'create_schema': True,
|
||||
'layout': self.layout_data,
|
||||
'position_x': 1,
|
||||
'position_y': 1
|
||||
}
|
||||
self.executeBadData(data=data, item=self.owned_id)
|
||||
data['item_data']['result'] = None
|
||||
response = self.executeCreated(data=data, item=self.owned_id)
|
||||
self.owned.refresh_from_db()
|
||||
new_operation = response.data['new_operation']
|
||||
schema = LibraryItem.objects.get(pk=new_operation['result'])
|
||||
self.assertEqual(schema.alias, data['item_data']['alias'])
|
||||
self.assertEqual(schema.title, data['item_data']['title'])
|
||||
self.assertEqual(schema.description, data['item_data']['description'])
|
||||
self.assertEqual(schema.visible, False)
|
||||
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
|
||||
self.assertEqual(schema.location, self.owned.model.location)
|
||||
self.assertIn(self.user2, schema.getQ_editors())
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||
def test_delete_operation(self):
|
||||
self.executeNotFound(item=self.invalid_id)
|
||||
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {
|
||||
'layout': self.layout_data
|
||||
}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['target'] = self.operation1.pk
|
||||
self.toggle_admin(True)
|
||||
self.executeBadData(data=data, item=self.unowned_id)
|
||||
self.logout()
|
||||
self.executeForbidden(data=data, item=self.owned_id)
|
||||
|
||||
self.login()
|
||||
response = self.executeOK(data=data)
|
||||
layout = response.data['layout']
|
||||
deleted_items = [item for item in layout['operations'] if item['id'] == data['target']]
|
||||
self.assertEqual(len(response.data['operations']), 2)
|
||||
self.assertEqual(len(deleted_items), 0)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/create-input', method='patch')
|
||||
def test_create_input(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {
|
||||
'layout': self.layout_data
|
||||
}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['target'] = self.operation1.pk
|
||||
self.toggle_admin(True)
|
||||
self.executeBadData(data=data, item=self.unowned_id)
|
||||
self.logout()
|
||||
self.executeForbidden(data=data, item=self.owned_id)
|
||||
|
||||
self.login()
|
||||
self.executeBadData(data=data, item=self.owned_id)
|
||||
|
||||
self.operation1.result = None
|
||||
self.operation1.description = 'TestComment'
|
||||
self.operation1.title = 'TestTitle'
|
||||
self.operation1.save()
|
||||
response = self.executeOK(data=data)
|
||||
self.operation1.refresh_from_db()
|
||||
|
||||
new_schema = response.data['new_schema']
|
||||
self.assertEqual(new_schema['id'], self.operation1.result.pk)
|
||||
self.assertEqual(new_schema['alias'], self.operation1.alias)
|
||||
self.assertEqual(new_schema['title'], self.operation1.title)
|
||||
self.assertEqual(new_schema['description'], self.operation1.description)
|
||||
|
||||
data['target'] = self.operation3.pk
|
||||
self.executeBadData(data=data)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
|
||||
def test_set_input_null(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {
|
||||
'layout': self.layout_data
|
||||
}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['target'] = self.operation1.pk
|
||||
data['input'] = None
|
||||
self.toggle_admin(True)
|
||||
self.executeBadData(data=data, item=self.unowned_id)
|
||||
self.logout()
|
||||
self.executeForbidden(data=data, item=self.owned_id)
|
||||
|
||||
self.login()
|
||||
response = self.executeOK(data=data)
|
||||
self.operation1.refresh_from_db()
|
||||
self.assertEqual(self.operation1.result, None)
|
||||
|
||||
data['input'] = self.ks1.model.pk
|
||||
self.ks1.model.alias = 'Test42'
|
||||
self.ks1.model.title = 'Test421'
|
||||
self.ks1.model.description = 'TestComment42'
|
||||
self.ks1.save()
|
||||
response = self.executeOK(data=data)
|
||||
self.operation1.refresh_from_db()
|
||||
self.assertEqual(self.operation1.result, self.ks1.model)
|
||||
self.assertEqual(self.operation1.alias, self.ks1.model.alias)
|
||||
self.assertEqual(self.operation1.title, self.ks1.model.title)
|
||||
self.assertEqual(self.operation1.description, self.ks1.model.description)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
|
||||
def test_set_input_change_schema(self):
|
||||
self.populateData()
|
||||
self.operation2.result = None
|
||||
|
||||
data = {
|
||||
'layout': self.layout_data,
|
||||
'target': self.operation1.pk,
|
||||
'input': self.ks2.model.pk
|
||||
}
|
||||
self.executeBadData(data=data, item=self.owned_id)
|
||||
|
||||
self.ks2.model.visible = False
|
||||
self.ks2.model.save(update_fields=['visible'])
|
||||
data = {
|
||||
'layout': self.layout_data,
|
||||
'target': self.operation2.pk,
|
||||
'input': None
|
||||
}
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.operation2.refresh_from_db()
|
||||
self.ks2.model.refresh_from_db()
|
||||
self.assertEqual(self.operation2.result, None)
|
||||
self.assertEqual(self.ks2.model.visible, True)
|
||||
|
||||
data = {
|
||||
'layout': self.layout_data,
|
||||
'target': self.operation1.pk,
|
||||
'input': self.ks2.model.pk
|
||||
}
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.operation1.refresh_from_db()
|
||||
self.assertEqual(self.operation1.result, self.ks2.model)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
|
||||
def test_update_operation(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
ks3 = RSForm.create(alias='KS3', title='Test3', owner=self.user)
|
||||
ks3x1 = ks3.insert_new('X1', term_resolved='X1_1')
|
||||
|
||||
data = {
|
||||
'target': self.operation3.pk,
|
||||
'item_data': {
|
||||
'alias': 'Test3 mod',
|
||||
'title': 'Test title mod',
|
||||
'description': 'Comment mod'
|
||||
},
|
||||
'layout': self.layout_data,
|
||||
'arguments': [self.operation2.pk, self.operation1.pk],
|
||||
'substitutions': [
|
||||
{
|
||||
'original': self.ks1X1.pk,
|
||||
'substitution': ks3x1.pk
|
||||
}
|
||||
]
|
||||
}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['substitutions'][0]['substitution'] = self.ks2X1.pk
|
||||
self.toggle_admin(True)
|
||||
self.executeBadData(data=data, item=self.unowned_id)
|
||||
self.logout()
|
||||
self.executeForbidden(data=data, item=self.owned_id)
|
||||
|
||||
self.login()
|
||||
response = self.executeOK(data=data)
|
||||
self.operation3.refresh_from_db()
|
||||
self.assertEqual(self.operation3.alias, data['item_data']['alias'])
|
||||
self.assertEqual(self.operation3.title, data['item_data']['title'])
|
||||
self.assertEqual(self.operation3.description, data['item_data']['description'])
|
||||
args = self.operation3.getQ_arguments().order_by('order')
|
||||
self.assertEqual(args[0].argument.pk, data['arguments'][0])
|
||||
self.assertEqual(args[0].order, 0)
|
||||
self.assertEqual(args[1].argument.pk, data['arguments'][1])
|
||||
self.assertEqual(args[1].order, 1)
|
||||
sub = self.operation3.getQ_substitutions()[0]
|
||||
self.assertEqual(sub.original.pk, data['substitutions'][0]['original'])
|
||||
self.assertEqual(sub.substitution.pk, data['substitutions'][0]['substitution'])
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
|
||||
def test_update_operation_sync(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {
|
||||
'target': self.operation1.pk,
|
||||
'item_data': {
|
||||
'alias': 'Test3 mod',
|
||||
'title': 'Test title mod',
|
||||
'description': 'Comment mod'
|
||||
},
|
||||
'layout': self.layout_data
|
||||
}
|
||||
|
||||
response = self.executeOK(data=data)
|
||||
self.operation1.refresh_from_db()
|
||||
self.assertEqual(self.operation1.alias, data['item_data']['alias'])
|
||||
self.assertEqual(self.operation1.title, data['item_data']['title'])
|
||||
self.assertEqual(self.operation1.description, data['item_data']['description'])
|
||||
self.assertEqual(self.operation1.result.alias, data['item_data']['alias'])
|
||||
self.assertEqual(self.operation1.result.title, data['item_data']['title'])
|
||||
self.assertEqual(self.operation1.result.description, data['item_data']['description'])
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
|
||||
def test_update_operation_invalid_substitution(self):
|
||||
self.populateData()
|
||||
|
||||
self.ks1X2 = self.ks1.insert_new('X2')
|
||||
|
||||
data = {
|
||||
'target': self.operation3.pk,
|
||||
'item_data': {
|
||||
'alias': 'Test3 mod',
|
||||
'title': 'Test title mod',
|
||||
'description': 'Comment mod'
|
||||
},
|
||||
'layout': self.layout_data,
|
||||
'arguments': [self.operation1.pk, self.operation2.pk],
|
||||
'substitutions': [
|
||||
{
|
||||
'original': self.ks1X1.pk,
|
||||
'substitution': self.ks2X1.pk
|
||||
},
|
||||
{
|
||||
'original': self.ks2X1.pk,
|
||||
'substitution': self.ks1X2.pk
|
||||
}
|
||||
]
|
||||
}
|
||||
self.executeBadData(data=data, item=self.owned_id)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/execute-operation', method='post')
|
||||
def test_execute_operation(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {
|
||||
'layout': self.layout_data,
|
||||
'target': self.operation1.pk
|
||||
}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['target'] = self.operation3.pk
|
||||
self.toggle_admin(True)
|
||||
self.executeBadData(data=data, item=self.unowned_id)
|
||||
self.logout()
|
||||
self.executeForbidden(data=data, item=self.owned_id)
|
||||
|
||||
self.login()
|
||||
self.executeOK(data=data)
|
||||
self.operation3.refresh_from_db()
|
||||
schema = self.operation3.result
|
||||
self.assertEqual(schema.alias, self.operation3.alias)
|
||||
self.assertEqual(schema.description, self.operation3.description)
|
||||
self.assertEqual(schema.title, self.operation3.title)
|
||||
self.assertEqual(schema.visible, False)
|
||||
items = list(RSForm(schema).constituents())
|
||||
self.assertEqual(len(items), 1)
|
||||
self.assertEqual(items[0].alias, 'X1')
|
||||
self.assertEqual(items[0].term_resolved, self.ks2X1.term_resolved)
|
|
@ -1,6 +1,6 @@
|
|||
''' Testing API: Operation Schema. '''
|
||||
from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType
|
||||
from apps.oss.models import Operation, OperationSchema, OperationType
|
||||
from apps.library.models import AccessPolicy, LibraryItemType
|
||||
from apps.oss.models import OperationSchema, OperationType
|
||||
from apps.rsform.models import Constituenta, RSForm
|
||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||
|
||||
|
@ -54,6 +54,14 @@ class TestOssViewset(EndpointTester):
|
|||
alias='3',
|
||||
operation_type=OperationType.SYNTHESIS
|
||||
)
|
||||
layout = self.owned.layout()
|
||||
layout.data = {'operations': [
|
||||
{'id': self.operation1.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation2.pk, 'x': 0, 'y': 0},
|
||||
{'id': self.operation3.pk, 'x': 0, 'y': 0},
|
||||
], 'blocks': []}
|
||||
layout.save()
|
||||
|
||||
self.owned.set_arguments(self.operation3.pk, [self.operation1, self.operation2])
|
||||
self.owned.set_substitutions(self.operation3.pk, [{
|
||||
'original': self.ks1X1,
|
||||
|
@ -74,9 +82,9 @@ class TestOssViewset(EndpointTester):
|
|||
|
||||
self.assertEqual(response.data['item_type'], LibraryItemType.OPERATION_SCHEMA)
|
||||
|
||||
self.assertEqual(len(response.data['items']), 3)
|
||||
self.assertEqual(response.data['items'][0]['id'], self.operation1.pk)
|
||||
self.assertEqual(response.data['items'][0]['operation_type'], self.operation1.operation_type)
|
||||
self.assertEqual(len(response.data['operations']), 3)
|
||||
self.assertEqual(response.data['operations'][0]['id'], self.operation1.pk)
|
||||
self.assertEqual(response.data['operations'][0]['operation_type'], self.operation1.operation_type)
|
||||
|
||||
self.assertEqual(len(response.data['substitutions']), 1)
|
||||
sub = response.data['substitutions'][0]
|
||||
|
@ -95,6 +103,12 @@ class TestOssViewset(EndpointTester):
|
|||
self.assertEqual(arguments[1]['operation'], self.operation3.pk)
|
||||
self.assertEqual(arguments[1]['argument'], self.operation2.pk)
|
||||
|
||||
layout = response.data['layout']
|
||||
self.assertEqual(layout['blocks'], [])
|
||||
self.assertEqual(layout['operations'][0], {'id': self.operation1.pk, 'x': 0, 'y': 0})
|
||||
self.assertEqual(layout['operations'][1], {'id': self.operation2.pk, 'x': 0, 'y': 0})
|
||||
self.assertEqual(layout['operations'][2], {'id': self.operation3.pk, 'x': 0, 'y': 0})
|
||||
|
||||
self.executeOK(item=self.unowned_id)
|
||||
self.executeForbidden(item=self.private_id)
|
||||
|
||||
|
@ -103,401 +117,32 @@ class TestOssViewset(EndpointTester):
|
|||
self.executeOK(item=self.unowned_id)
|
||||
self.executeForbidden(item=self.private_id)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/update-positions', method='patch')
|
||||
def test_update_positions(self):
|
||||
@decl_endpoint('/api/oss/{item}/update-layout', method='patch')
|
||||
def test_update_layout(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {'positions': []}
|
||||
data = {'operations': [], 'blocks': []}
|
||||
self.executeOK(data=data)
|
||||
|
||||
data = {'positions': [
|
||||
{'id': self.operation1.pk, 'position_x': 42.1, 'position_y': 1337},
|
||||
{'id': self.operation2.pk, 'position_x': 36.1, 'position_y': 1437},
|
||||
{'id': self.invalid_id, 'position_x': 31, 'position_y': 12},
|
||||
]}
|
||||
data = {
|
||||
'operations': [
|
||||
{'id': self.operation1.pk, 'x': 42.1, 'y': 1337},
|
||||
{'id': self.operation2.pk, 'x': 36.1, 'y': 1437},
|
||||
{'id': self.operation3.pk, 'x': 36.1, 'y': 1435}
|
||||
], 'blocks': []
|
||||
}
|
||||
self.toggle_admin(True)
|
||||
self.executeOK(data=data, item=self.unowned_id)
|
||||
self.operation1.refresh_from_db()
|
||||
self.assertNotEqual(self.operation1.position_x, data['positions'][0]['position_x'])
|
||||
self.assertNotEqual(self.operation1.position_y, data['positions'][0]['position_y'])
|
||||
|
||||
self.toggle_admin(False)
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.operation1.refresh_from_db()
|
||||
self.operation2.refresh_from_db()
|
||||
self.assertEqual(self.operation1.position_x, data['positions'][0]['position_x'])
|
||||
self.assertEqual(self.operation1.position_y, data['positions'][0]['position_y'])
|
||||
self.assertEqual(self.operation2.position_x, data['positions'][1]['position_x'])
|
||||
self.assertEqual(self.operation2.position_y, data['positions'][1]['position_y'])
|
||||
self.owned.refresh_from_db()
|
||||
self.assertEqual(self.owned.layout().data, data)
|
||||
|
||||
self.executeForbidden(data=data, item=self.unowned_id)
|
||||
self.executeForbidden(data=data, item=self.private_id)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||
def test_create_operation(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {
|
||||
'item_data': {
|
||||
'alias': 'Test3',
|
||||
'title': 'Test title',
|
||||
'description': 'Тест кириллицы',
|
||||
'position_x': 1,
|
||||
'position_y': 1,
|
||||
},
|
||||
'positions': [
|
||||
{'id': self.operation1.pk, 'position_x': 42.1, 'position_y': 1337}
|
||||
]
|
||||
}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['item_data']['operation_type'] = 'invalid'
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['item_data']['operation_type'] = OperationType.INPUT
|
||||
self.executeNotFound(data=data, item=self.invalid_id)
|
||||
|
||||
response = self.executeCreated(data=data, item=self.owned_id)
|
||||
self.assertEqual(len(response.data['oss']['items']), 4)
|
||||
new_operation = response.data['new_operation']
|
||||
self.assertEqual(new_operation['alias'], data['item_data']['alias'])
|
||||
self.assertEqual(new_operation['operation_type'], data['item_data']['operation_type'])
|
||||
self.assertEqual(new_operation['title'], data['item_data']['title'])
|
||||
self.assertEqual(new_operation['description'], data['item_data']['description'])
|
||||
self.assertEqual(new_operation['position_x'], data['item_data']['position_x'])
|
||||
self.assertEqual(new_operation['position_y'], data['item_data']['position_y'])
|
||||
self.assertEqual(new_operation['result'], None)
|
||||
self.operation1.refresh_from_db()
|
||||
self.assertEqual(self.operation1.position_x, data['positions'][0]['position_x'])
|
||||
self.assertEqual(self.operation1.position_y, data['positions'][0]['position_y'])
|
||||
|
||||
self.executeForbidden(data=data, item=self.unowned_id)
|
||||
self.toggle_admin(True)
|
||||
self.executeCreated(data=data, item=self.unowned_id)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||
def test_create_operation_arguments(self):
|
||||
self.populateData()
|
||||
data = {
|
||||
'item_data': {
|
||||
'alias': 'Test4',
|
||||
'operation_type': OperationType.SYNTHESIS
|
||||
},
|
||||
'positions': [],
|
||||
'arguments': [self.operation1.pk, self.operation3.pk]
|
||||
}
|
||||
response = self.executeCreated(data=data, item=self.owned_id)
|
||||
self.owned.refresh_from_db()
|
||||
new_operation = response.data['new_operation']
|
||||
arguments = self.owned.arguments()
|
||||
self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation1))
|
||||
self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation3))
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||
def test_create_operation_result(self):
|
||||
self.populateData()
|
||||
|
||||
self.operation1.result = None
|
||||
self.operation1.save()
|
||||
|
||||
data = {
|
||||
'item_data': {
|
||||
'alias': 'Test4',
|
||||
'operation_type': OperationType.INPUT,
|
||||
'result': self.ks1.model.pk
|
||||
},
|
||||
'positions': [],
|
||||
}
|
||||
response = self.executeCreated(data=data, item=self.owned_id)
|
||||
self.owned.refresh_from_db()
|
||||
new_operation = response.data['new_operation']
|
||||
self.assertEqual(new_operation['result'], self.ks1.model.pk)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||
def test_create_operation_schema(self):
|
||||
self.populateData()
|
||||
Editor.add(self.owned.model.pk, self.user2.pk)
|
||||
data = {
|
||||
'item_data': {
|
||||
'alias': 'Test4',
|
||||
'title': 'Test title',
|
||||
'description': 'Comment',
|
||||
'operation_type': OperationType.INPUT,
|
||||
'result': self.ks1.model.pk
|
||||
},
|
||||
'create_schema': True,
|
||||
'positions': [],
|
||||
}
|
||||
self.executeBadData(data=data, item=self.owned_id)
|
||||
data['item_data']['result'] = None
|
||||
response = self.executeCreated(data=data, item=self.owned_id)
|
||||
self.owned.refresh_from_db()
|
||||
new_operation = response.data['new_operation']
|
||||
schema = LibraryItem.objects.get(pk=new_operation['result'])
|
||||
self.assertEqual(schema.alias, data['item_data']['alias'])
|
||||
self.assertEqual(schema.title, data['item_data']['title'])
|
||||
self.assertEqual(schema.description, data['item_data']['description'])
|
||||
self.assertEqual(schema.visible, False)
|
||||
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
|
||||
self.assertEqual(schema.location, self.owned.model.location)
|
||||
self.assertIn(self.user2, schema.getQ_editors())
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||
def test_delete_operation(self):
|
||||
self.executeNotFound(item=self.invalid_id)
|
||||
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {
|
||||
'positions': []
|
||||
}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['target'] = self.operation1.pk
|
||||
self.toggle_admin(True)
|
||||
self.executeBadData(data=data, item=self.unowned_id)
|
||||
self.logout()
|
||||
self.executeForbidden(data=data, item=self.owned_id)
|
||||
|
||||
self.login()
|
||||
response = self.executeOK(data=data)
|
||||
self.assertEqual(len(response.data['items']), 2)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/create-input', method='patch')
|
||||
def test_create_input(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {
|
||||
'positions': []
|
||||
}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['target'] = self.operation1.pk
|
||||
self.toggle_admin(True)
|
||||
self.executeBadData(data=data, item=self.unowned_id)
|
||||
self.logout()
|
||||
self.executeForbidden(data=data, item=self.owned_id)
|
||||
|
||||
self.login()
|
||||
self.executeBadData(data=data, item=self.owned_id)
|
||||
|
||||
self.operation1.result = None
|
||||
self.operation1.description = 'TestComment'
|
||||
self.operation1.title = 'TestTitle'
|
||||
self.operation1.save()
|
||||
response = self.executeOK(data=data)
|
||||
self.operation1.refresh_from_db()
|
||||
|
||||
new_schema = response.data['new_schema']
|
||||
self.assertEqual(new_schema['id'], self.operation1.result.pk)
|
||||
self.assertEqual(new_schema['alias'], self.operation1.alias)
|
||||
self.assertEqual(new_schema['title'], self.operation1.title)
|
||||
self.assertEqual(new_schema['description'], self.operation1.description)
|
||||
|
||||
data['target'] = self.operation3.pk
|
||||
self.executeBadData(data=data)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
|
||||
def test_set_input_null(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {
|
||||
'positions': []
|
||||
}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['target'] = self.operation1.pk
|
||||
data['input'] = None
|
||||
self.toggle_admin(True)
|
||||
self.executeBadData(data=data, item=self.unowned_id)
|
||||
self.logout()
|
||||
self.executeForbidden(data=data, item=self.owned_id)
|
||||
|
||||
self.login()
|
||||
response = self.executeOK(data=data)
|
||||
self.operation1.refresh_from_db()
|
||||
self.assertEqual(self.operation1.result, None)
|
||||
|
||||
data['input'] = self.ks1.model.pk
|
||||
self.ks1.model.alias = 'Test42'
|
||||
self.ks1.model.title = 'Test421'
|
||||
self.ks1.model.description = 'TestComment42'
|
||||
self.ks1.save()
|
||||
response = self.executeOK(data=data)
|
||||
self.operation1.refresh_from_db()
|
||||
self.assertEqual(self.operation1.result, self.ks1.model)
|
||||
self.assertEqual(self.operation1.alias, self.ks1.model.alias)
|
||||
self.assertEqual(self.operation1.title, self.ks1.model.title)
|
||||
self.assertEqual(self.operation1.description, self.ks1.model.description)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
|
||||
def test_set_input_change_schema(self):
|
||||
self.populateData()
|
||||
self.operation2.result = None
|
||||
|
||||
data = {
|
||||
'positions': [],
|
||||
'target': self.operation1.pk,
|
||||
'input': self.ks2.model.pk
|
||||
}
|
||||
self.executeBadData(data=data, item=self.owned_id)
|
||||
|
||||
self.ks2.model.visible = False
|
||||
self.ks2.model.save(update_fields=['visible'])
|
||||
data = {
|
||||
'positions': [],
|
||||
'target': self.operation2.pk,
|
||||
'input': None
|
||||
}
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.operation2.refresh_from_db()
|
||||
self.ks2.model.refresh_from_db()
|
||||
self.assertEqual(self.operation2.result, None)
|
||||
self.assertEqual(self.ks2.model.visible, True)
|
||||
|
||||
data = {
|
||||
'positions': [],
|
||||
'target': self.operation1.pk,
|
||||
'input': self.ks2.model.pk
|
||||
}
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.operation1.refresh_from_db()
|
||||
self.assertEqual(self.operation1.result, self.ks2.model)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
|
||||
def test_update_operation(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
ks3 = RSForm.create(alias='KS3', title='Test3', owner=self.user)
|
||||
ks3x1 = ks3.insert_new('X1', term_resolved='X1_1')
|
||||
|
||||
data = {
|
||||
'target': self.operation3.pk,
|
||||
'item_data': {
|
||||
'alias': 'Test3 mod',
|
||||
'title': 'Test title mod',
|
||||
'description': 'Comment mod'
|
||||
},
|
||||
'positions': [],
|
||||
'arguments': [self.operation2.pk, self.operation1.pk],
|
||||
'substitutions': [
|
||||
{
|
||||
'original': self.ks1X1.pk,
|
||||
'substitution': ks3x1.pk
|
||||
}
|
||||
]
|
||||
}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['substitutions'][0]['substitution'] = self.ks2X1.pk
|
||||
self.toggle_admin(True)
|
||||
self.executeBadData(data=data, item=self.unowned_id)
|
||||
self.logout()
|
||||
self.executeForbidden(data=data, item=self.owned_id)
|
||||
|
||||
self.login()
|
||||
response = self.executeOK(data=data)
|
||||
self.operation3.refresh_from_db()
|
||||
self.assertEqual(self.operation3.alias, data['item_data']['alias'])
|
||||
self.assertEqual(self.operation3.title, data['item_data']['title'])
|
||||
self.assertEqual(self.operation3.description, data['item_data']['description'])
|
||||
args = self.operation3.getQ_arguments().order_by('order')
|
||||
self.assertEqual(args[0].argument.pk, data['arguments'][0])
|
||||
self.assertEqual(args[0].order, 0)
|
||||
self.assertEqual(args[1].argument.pk, data['arguments'][1])
|
||||
self.assertEqual(args[1].order, 1)
|
||||
sub = self.operation3.getQ_substitutions()[0]
|
||||
self.assertEqual(sub.original.pk, data['substitutions'][0]['original'])
|
||||
self.assertEqual(sub.substitution.pk, data['substitutions'][0]['substitution'])
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
|
||||
def test_update_operation_sync(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {
|
||||
'target': self.operation1.pk,
|
||||
'item_data': {
|
||||
'alias': 'Test3 mod',
|
||||
'title': 'Test title mod',
|
||||
'description': 'Comment mod'
|
||||
},
|
||||
'positions': [],
|
||||
}
|
||||
|
||||
response = self.executeOK(data=data)
|
||||
self.operation1.refresh_from_db()
|
||||
self.assertEqual(self.operation1.alias, data['item_data']['alias'])
|
||||
self.assertEqual(self.operation1.title, data['item_data']['title'])
|
||||
self.assertEqual(self.operation1.description, data['item_data']['description'])
|
||||
self.assertEqual(self.operation1.result.alias, data['item_data']['alias'])
|
||||
self.assertEqual(self.operation1.result.title, data['item_data']['title'])
|
||||
self.assertEqual(self.operation1.result.description, data['item_data']['description'])
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
|
||||
def test_update_operation_invalid_substitution(self):
|
||||
self.populateData()
|
||||
|
||||
self.ks1X2 = self.ks1.insert_new('X2')
|
||||
|
||||
data = {
|
||||
'target': self.operation3.pk,
|
||||
'item_data': {
|
||||
'alias': 'Test3 mod',
|
||||
'title': 'Test title mod',
|
||||
'description': 'Comment mod'
|
||||
},
|
||||
'positions': [],
|
||||
'arguments': [self.operation1.pk, self.operation2.pk],
|
||||
'substitutions': [
|
||||
{
|
||||
'original': self.ks1X1.pk,
|
||||
'substitution': self.ks2X1.pk
|
||||
},
|
||||
{
|
||||
'original': self.ks2X1.pk,
|
||||
'substitution': self.ks1X2.pk
|
||||
}
|
||||
]
|
||||
}
|
||||
self.executeBadData(data=data, item=self.owned_id)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/execute-operation', method='post')
|
||||
def test_execute_operation(self):
|
||||
self.populateData()
|
||||
self.executeBadData(item=self.owned_id)
|
||||
|
||||
data = {
|
||||
'positions': [],
|
||||
'target': self.operation1.pk
|
||||
}
|
||||
self.executeBadData(data=data)
|
||||
|
||||
data['target'] = self.operation3.pk
|
||||
self.toggle_admin(True)
|
||||
self.executeBadData(data=data, item=self.unowned_id)
|
||||
self.logout()
|
||||
self.executeForbidden(data=data, item=self.owned_id)
|
||||
|
||||
self.login()
|
||||
self.executeOK(data=data)
|
||||
self.operation3.refresh_from_db()
|
||||
schema = self.operation3.result
|
||||
self.assertEqual(schema.alias, self.operation3.alias)
|
||||
self.assertEqual(schema.description, self.operation3.description)
|
||||
self.assertEqual(schema.title, self.operation3.title)
|
||||
self.assertEqual(schema.visible, False)
|
||||
items = list(RSForm(schema).constituents())
|
||||
self.assertEqual(len(items), 1)
|
||||
self.assertEqual(items[0].alias, 'X1')
|
||||
self.assertEqual(items[0].term_resolved, self.ks2X1.term_resolved)
|
||||
|
||||
@decl_endpoint('/api/oss/get-predecessor', method='post')
|
||||
def test_get_predecessor(self):
|
||||
self.populateData()
|
||||
|
|
|
@ -36,9 +36,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
def get_permissions(self):
|
||||
''' Determine permission class. '''
|
||||
if self.action in [
|
||||
'update_layout',
|
||||
'create_operation',
|
||||
'delete_operation',
|
||||
'update_positions',
|
||||
'create_input',
|
||||
'set_input',
|
||||
'update_operation',
|
||||
|
@ -73,21 +73,21 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
)
|
||||
|
||||
@extend_schema(
|
||||
summary='update positions',
|
||||
summary='update layout',
|
||||
tags=['OSS'],
|
||||
request=s.PositionsSerializer,
|
||||
request=s.LayoutSerializer,
|
||||
responses={
|
||||
c.HTTP_200_OK: None,
|
||||
c.HTTP_403_FORBIDDEN: None,
|
||||
c.HTTP_404_NOT_FOUND: None
|
||||
}
|
||||
)
|
||||
@action(detail=True, methods=['patch'], url_path='update-positions')
|
||||
def update_positions(self, request: Request, pk) -> HttpResponse:
|
||||
''' Endpoint: Update operations positions. '''
|
||||
serializer = s.PositionsSerializer(data=request.data)
|
||||
@action(detail=True, methods=['patch'], url_path='update-layout')
|
||||
def update_layout(self, request: Request, pk) -> HttpResponse:
|
||||
''' Endpoint: Update schema layout. '''
|
||||
serializer = s.LayoutSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
m.OperationSchema(self.get_object()).update_positions(serializer.validated_data['positions'])
|
||||
m.OperationSchema(self.get_object()).update_layout(serializer.validated_data)
|
||||
return Response(status=c.HTTP_200_OK)
|
||||
|
||||
@extend_schema(
|
||||
|
@ -108,9 +108,16 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
layout = serializer.validated_data['layout']
|
||||
with transaction.atomic():
|
||||
oss.update_positions(serializer.validated_data['positions'])
|
||||
new_operation = oss.create_operation(**serializer.validated_data['item_data'])
|
||||
layout['operations'].append({
|
||||
'id': new_operation.pk,
|
||||
'x': serializer.validated_data['position_x'],
|
||||
'y': serializer.validated_data['position_y']
|
||||
})
|
||||
oss.update_layout(layout)
|
||||
|
||||
schema = new_operation.result
|
||||
if schema is not None:
|
||||
connected_operations = \
|
||||
|
@ -164,9 +171,11 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
oss = m.OperationSchema(self.get_object())
|
||||
operation = cast(m.Operation, serializer.validated_data['target'])
|
||||
old_schema = operation.result
|
||||
layout = serializer.validated_data['layout']
|
||||
layout['operations'] = [x for x in layout['operations'] if x['id'] != operation.pk]
|
||||
with transaction.atomic():
|
||||
oss.update_positions(serializer.validated_data['positions'])
|
||||
oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents'])
|
||||
oss.update_layout(layout)
|
||||
if old_schema is not None:
|
||||
if serializer.validated_data['delete_schema']:
|
||||
m.PropagationFacade.before_delete_schema(old_schema)
|
||||
|
@ -211,7 +220,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
with transaction.atomic():
|
||||
oss.update_positions(serializer.validated_data['positions'])
|
||||
oss.update_layout(serializer.validated_data['layout'])
|
||||
schema = oss.create_input(operation)
|
||||
|
||||
return Response(
|
||||
|
@ -262,7 +271,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
if old_schema.is_synced(oss.model):
|
||||
old_schema.visible = True
|
||||
old_schema.save(update_fields=['visible'])
|
||||
oss.update_positions(serializer.validated_data['positions'])
|
||||
oss.update_layout(serializer.validated_data['layout'])
|
||||
oss.set_input(target_operation.pk, schema)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
|
@ -292,7 +301,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
with transaction.atomic():
|
||||
oss.update_positions(serializer.validated_data['positions'])
|
||||
oss.update_layout(serializer.validated_data['layout'])
|
||||
operation.alias = serializer.validated_data['item_data']['alias']
|
||||
operation.title = serializer.validated_data['item_data']['title']
|
||||
operation.description = serializer.validated_data['item_data']['description']
|
||||
|
@ -346,7 +355,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
with transaction.atomic():
|
||||
oss.update_positions(serializer.validated_data['positions'])
|
||||
oss.update_layout(serializer.validated_data['layout'])
|
||||
oss.execute_operation(operation)
|
||||
|
||||
return Response(
|
||||
|
|
|
@ -20,7 +20,7 @@ export const useSetAccessPolicy = () => {
|
|||
client.setQueryData(ossKey, { ...ossData, access_policy: variables.policy });
|
||||
return Promise.allSettled([
|
||||
client.invalidateQueries({ queryKey: KEYS.composite.libraryList }),
|
||||
...ossData.items
|
||||
...ossData.operations
|
||||
.map(item => {
|
||||
if (!item.result) {
|
||||
return;
|
||||
|
|
|
@ -18,7 +18,7 @@ export const useSetEditors = () => {
|
|||
if (ossData) {
|
||||
client.setQueryData(ossKey, { ...ossData, editors: variables.editors });
|
||||
return Promise.allSettled(
|
||||
ossData.items
|
||||
ossData.operations
|
||||
.map(item => {
|
||||
if (!item.result) {
|
||||
return;
|
||||
|
|
|
@ -20,7 +20,7 @@ export const useSetLocation = () => {
|
|||
client.setQueryData(ossKey, { ...ossData, location: variables.location });
|
||||
return Promise.allSettled([
|
||||
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
|
||||
...ossData.items
|
||||
...ossData.operations
|
||||
.map(item => {
|
||||
if (!item.result) {
|
||||
return;
|
||||
|
|
|
@ -20,7 +20,7 @@ export const useSetOwner = () => {
|
|||
client.setQueryData(ossKey, { ...ossData, owner: variables.owner });
|
||||
return Promise.allSettled([
|
||||
client.invalidateQueries({ queryKey: libraryApi.libraryListKey }),
|
||||
...ossData.items
|
||||
...ossData.operations
|
||||
.map(item => {
|
||||
if (!item.result) {
|
||||
return;
|
||||
|
|
|
@ -12,9 +12,9 @@ import {
|
|||
type IOperationCreatedResponse,
|
||||
type IOperationCreateDTO,
|
||||
type IOperationDeleteDTO,
|
||||
type IOperationPosition,
|
||||
type IOperationSchemaDTO,
|
||||
type IOperationUpdateDTO,
|
||||
type IOssLayout,
|
||||
type ITargetOperation,
|
||||
schemaConstituentaReference,
|
||||
schemaOperationCreatedResponse,
|
||||
|
@ -39,19 +39,11 @@ export const ossApi = {
|
|||
});
|
||||
},
|
||||
|
||||
updatePositions: ({
|
||||
itemID,
|
||||
positions,
|
||||
isSilent
|
||||
}: {
|
||||
itemID: number;
|
||||
positions: IOperationPosition[];
|
||||
isSilent?: boolean;
|
||||
}) =>
|
||||
updateLayout: ({ itemID, data, isSilent }: { itemID: number; data: IOssLayout; isSilent?: boolean }) =>
|
||||
axiosPatch({
|
||||
endpoint: `/api/oss/${itemID}/update-positions`,
|
||||
endpoint: `/api/oss/${itemID}/update-layout`,
|
||||
request: {
|
||||
data: { positions: positions },
|
||||
data: data,
|
||||
successMessage: isSilent ? undefined : infoMsg.changesSaved
|
||||
}
|
||||
}),
|
||||
|
|
|
@ -41,7 +41,7 @@ export class OssLoader {
|
|||
}
|
||||
|
||||
private prepareLookups() {
|
||||
this.oss.items.forEach(operation => {
|
||||
this.oss.operations.forEach(operation => {
|
||||
this.operationByID.set(operation.id, operation);
|
||||
this.graph.addNode(operation.id);
|
||||
});
|
||||
|
@ -52,13 +52,16 @@ export class OssLoader {
|
|||
}
|
||||
|
||||
private extractSchemas() {
|
||||
this.schemaIDs = this.oss.items.map(operation => operation.result).filter(item => item !== null);
|
||||
this.schemaIDs = this.oss.operations.map(operation => operation.result).filter(item => item !== null);
|
||||
}
|
||||
|
||||
private inferOperationAttributes() {
|
||||
this.graph.topologicalOrder().forEach(operationID => {
|
||||
const operation = this.operationByID.get(operationID)!;
|
||||
const schema = this.items.find(item => item.id === operation.result);
|
||||
const position = this.oss.layout.operations.find(item => item.id === operationID);
|
||||
operation.x = position?.x ?? 0;
|
||||
operation.y = position?.y ?? 0;
|
||||
operation.is_consolidation = this.inferConsolidation(operationID);
|
||||
operation.is_owned = !schema || (schema.owner === this.oss.owner && schema.location === this.oss.location);
|
||||
operation.substitutions = this.oss.substitutions.filter(item => item.operation === operationID);
|
||||
|
@ -82,7 +85,7 @@ export class OssLoader {
|
|||
}
|
||||
|
||||
private calculateStats(): IOperationSchemaStats {
|
||||
const items = this.oss.items;
|
||||
const items = this.oss.operations;
|
||||
return {
|
||||
count_operations: items.length,
|
||||
count_inputs: items.filter(item => item.operation_type === OperationType.INPUT).length,
|
||||
|
|
|
@ -23,8 +23,8 @@ export type IOperationDTO = z.infer<typeof schemaOperation>;
|
|||
/** Represents backend data for {@link IOperationSchema}. */
|
||||
export type IOperationSchemaDTO = z.infer<typeof schemaOperationSchema>;
|
||||
|
||||
/** Represents {@link IOperation} position. */
|
||||
export type IOperationPosition = z.infer<typeof schemaOperationPosition>;
|
||||
/** Represents {@link schemaOperation} layout. */
|
||||
export type IOssLayout = z.infer<typeof schemaOssLayout>;
|
||||
|
||||
/** Represents {@link IOperation} data, used in creation process. */
|
||||
export type IOperationCreateDTO = z.infer<typeof schemaOperationCreate>;
|
||||
|
@ -35,7 +35,7 @@ export type IOperationCreatedResponse = z.infer<typeof schemaOperationCreatedRes
|
|||
* Represents target {@link IOperation}.
|
||||
*/
|
||||
export interface ITargetOperation {
|
||||
positions: IOperationPosition[];
|
||||
layout: IOssLayout;
|
||||
target: number;
|
||||
}
|
||||
|
||||
|
@ -69,9 +69,7 @@ export const schemaOperation = z.strictObject({
|
|||
title: z.string(),
|
||||
description: z.string(),
|
||||
|
||||
position_x: z.number(),
|
||||
position_y: z.number(),
|
||||
|
||||
parent: z.number().nullable(),
|
||||
result: z.number().nullable()
|
||||
});
|
||||
|
||||
|
@ -83,9 +81,21 @@ export const schemaCstSubstituteInfo = schemaCstSubstitute.extend({
|
|||
substitution_term: z.string()
|
||||
});
|
||||
|
||||
export const schemaPosition = z.strictObject({
|
||||
id: z.number(),
|
||||
x: z.number(),
|
||||
y: z.number()
|
||||
});
|
||||
|
||||
export const schemaOssLayout = z.strictObject({
|
||||
operations: z.array(schemaPosition),
|
||||
blocks: z.array(schemaPosition)
|
||||
});
|
||||
|
||||
export const schemaOperationSchema = schemaLibraryItem.extend({
|
||||
editors: z.number().array(),
|
||||
items: z.array(schemaOperation),
|
||||
operations: z.array(schemaOperation),
|
||||
layout: schemaOssLayout,
|
||||
arguments: z
|
||||
.object({
|
||||
operation: z.number(),
|
||||
|
@ -95,23 +105,18 @@ export const schemaOperationSchema = schemaLibraryItem.extend({
|
|||
substitutions: z.array(schemaCstSubstituteInfo)
|
||||
});
|
||||
|
||||
export const schemaOperationPosition = z.strictObject({
|
||||
id: z.number(),
|
||||
position_x: z.number(),
|
||||
position_y: z.number()
|
||||
});
|
||||
|
||||
export const schemaOperationCreate = z.strictObject({
|
||||
positions: z.array(schemaOperationPosition),
|
||||
layout: schemaOssLayout,
|
||||
item_data: z.strictObject({
|
||||
alias: z.string().nonempty(),
|
||||
operation_type: schemaOperationType,
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
position_x: z.number(),
|
||||
position_y: z.number(),
|
||||
parent: z.number().nullable(),
|
||||
result: z.number().nullable()
|
||||
}),
|
||||
position_x: z.number(),
|
||||
position_y: z.number(),
|
||||
arguments: z.array(z.number()),
|
||||
create_schema: z.boolean()
|
||||
});
|
||||
|
@ -123,14 +128,14 @@ export const schemaOperationCreatedResponse = z.strictObject({
|
|||
|
||||
export const schemaOperationDelete = z.strictObject({
|
||||
target: z.number(),
|
||||
positions: z.array(schemaOperationPosition),
|
||||
layout: schemaOssLayout,
|
||||
keep_constituents: z.boolean(),
|
||||
delete_schema: z.boolean()
|
||||
});
|
||||
|
||||
export const schemaInputUpdate = z.strictObject({
|
||||
target: z.number(),
|
||||
positions: z.array(schemaOperationPosition),
|
||||
layout: schemaOssLayout,
|
||||
input: z.number().nullable()
|
||||
});
|
||||
|
||||
|
@ -141,7 +146,7 @@ export const schemaInputCreatedResponse = z.strictObject({
|
|||
|
||||
export const schemaOperationUpdate = z.strictObject({
|
||||
target: z.number(),
|
||||
positions: z.array(schemaOperationPosition),
|
||||
layout: schemaOssLayout,
|
||||
item_data: z.strictObject({
|
||||
alias: z.string().nonempty(errorMsg.requiredField),
|
||||
title: z.string(),
|
||||
|
|
|
@ -14,7 +14,7 @@ export const useOperationUpdate = () => {
|
|||
mutationFn: ossApi.operationUpdate,
|
||||
onSuccess: (data, variables) => {
|
||||
client.setQueryData(KEYS.composite.ossItem({ itemID: data.id }), data);
|
||||
const schemaID = data.items.find(item => item.id === variables.data.target)?.result;
|
||||
const schemaID = data.operations.find(item => item.id === variables.data.target)?.result;
|
||||
if (!schemaID) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -5,21 +5,33 @@ import { useUpdateTimestamp } from '@/features/library/backend/use-update-timest
|
|||
import { KEYS } from '@/backend/configuration';
|
||||
|
||||
import { ossApi } from './api';
|
||||
import { type IOperationPosition } from './types';
|
||||
import { type IOperationSchemaDTO, type IOssLayout } from './types';
|
||||
|
||||
export const useUpdatePositions = () => {
|
||||
export const useUpdateLayout = () => {
|
||||
const client = useQueryClient();
|
||||
const { updateTimestamp } = useUpdateTimestamp();
|
||||
const mutation = useMutation({
|
||||
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'update-positions'],
|
||||
mutationFn: ossApi.updatePositions,
|
||||
onSuccess: (_, variables) => updateTimestamp(variables.itemID),
|
||||
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'update-layout'],
|
||||
mutationFn: ossApi.updateLayout,
|
||||
onSuccess: (_, variables) => {
|
||||
updateTimestamp(variables.itemID);
|
||||
client.setQueryData(
|
||||
ossApi.getOssQueryOptions({ itemID: variables.itemID }).queryKey,
|
||||
(prev: IOperationSchemaDTO | undefined) =>
|
||||
!prev
|
||||
? prev
|
||||
: {
|
||||
...prev,
|
||||
layout: variables.data
|
||||
}
|
||||
);
|
||||
},
|
||||
onError: () => client.invalidateQueries()
|
||||
});
|
||||
return {
|
||||
updatePositions: (data: {
|
||||
updateLayout: (data: {
|
||||
itemID: number; //
|
||||
positions: IOperationPosition[];
|
||||
data: IOssLayout;
|
||||
isSilent?: boolean;
|
||||
}) => mutation.mutateAsync(data)
|
||||
};
|
|
@ -13,7 +13,7 @@ import { Label } from '@/components/input';
|
|||
import { ModalForm } from '@/components/modal';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { type IInputUpdateDTO, type IOperationPosition, schemaInputUpdate } from '../backend/types';
|
||||
import { type IInputUpdateDTO, type IOssLayout, schemaInputUpdate } from '../backend/types';
|
||||
import { useInputUpdate } from '../backend/use-input-update';
|
||||
import { type IOperation, type IOperationSchema } from '../models/oss';
|
||||
import { sortItemsForOSS } from '../models/oss-api';
|
||||
|
@ -21,18 +21,18 @@ import { sortItemsForOSS } from '../models/oss-api';
|
|||
export interface DlgChangeInputSchemaProps {
|
||||
oss: IOperationSchema;
|
||||
target: IOperation;
|
||||
positions: IOperationPosition[];
|
||||
layout: IOssLayout;
|
||||
}
|
||||
|
||||
export function DlgChangeInputSchema() {
|
||||
const { oss, target, positions } = useDialogsStore(state => state.props as DlgChangeInputSchemaProps);
|
||||
const { oss, target, layout } = useDialogsStore(state => state.props as DlgChangeInputSchemaProps);
|
||||
const { inputUpdate } = useInputUpdate();
|
||||
|
||||
const { setValue, handleSubmit, control } = useForm<IInputUpdateDTO>({
|
||||
resolver: zodResolver(schemaInputUpdate),
|
||||
defaultValues: {
|
||||
target: target.id,
|
||||
positions: positions,
|
||||
layout: layout,
|
||||
input: target.result
|
||||
}
|
||||
});
|
||||
|
|
|
@ -10,12 +10,7 @@ import { ModalForm } from '@/components/modal';
|
|||
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import {
|
||||
type IOperationCreateDTO,
|
||||
type IOperationPosition,
|
||||
OperationType,
|
||||
schemaOperationCreate
|
||||
} from '../../backend/types';
|
||||
import { type IOperationCreateDTO, type IOssLayout, OperationType, schemaOperationCreate } from '../../backend/types';
|
||||
import { useOperationCreate } from '../../backend/use-operation-create';
|
||||
import { describeOperationType, labelOperationType } from '../../labels';
|
||||
import { type IOperationSchema } from '../../models/oss';
|
||||
|
@ -26,7 +21,7 @@ import { TabSynthesisOperation } from './tab-synthesis-operation';
|
|||
|
||||
export interface DlgCreateOperationProps {
|
||||
oss: IOperationSchema;
|
||||
positions: IOperationPosition[];
|
||||
layout: IOssLayout;
|
||||
initialInputs: number[];
|
||||
defaultX: number;
|
||||
defaultY: number;
|
||||
|
@ -42,7 +37,7 @@ export type TabID = (typeof TabID)[keyof typeof TabID];
|
|||
export function DlgCreateOperation() {
|
||||
const { operationCreate } = useOperationCreate();
|
||||
|
||||
const { oss, positions, initialInputs, onCreate, defaultX, defaultY } = useDialogsStore(
|
||||
const { oss, layout, initialInputs, onCreate, defaultX, defaultY } = useDialogsStore(
|
||||
state => state.props as DlgCreateOperationProps
|
||||
);
|
||||
|
||||
|
@ -51,30 +46,31 @@ export function DlgCreateOperation() {
|
|||
defaultValues: {
|
||||
item_data: {
|
||||
operation_type: initialInputs.length === 0 ? OperationType.INPUT : OperationType.SYNTHESIS,
|
||||
result: null,
|
||||
position_x: defaultX,
|
||||
position_y: defaultY,
|
||||
alias: '',
|
||||
title: '',
|
||||
description: ''
|
||||
description: '',
|
||||
result: null,
|
||||
parent: null
|
||||
},
|
||||
position_x: defaultX,
|
||||
position_y: defaultY,
|
||||
arguments: initialInputs,
|
||||
create_schema: false,
|
||||
positions: positions
|
||||
layout: layout
|
||||
},
|
||||
mode: 'onChange'
|
||||
});
|
||||
const alias = useWatch({ control: methods.control, name: 'item_data.alias' });
|
||||
const [activeTab, setActiveTab] = useState(initialInputs.length === 0 ? TabID.INPUT : TabID.SYNTHESIS);
|
||||
const isValid = !!alias && !oss.items.some(operation => operation.alias === alias);
|
||||
const isValid = !!alias && !oss.operations.some(operation => operation.alias === alias);
|
||||
|
||||
function onSubmit(data: IOperationCreateDTO) {
|
||||
const target = calculateInsertPosition(oss, data.arguments, positions, {
|
||||
const target = calculateInsertPosition(oss, data.arguments, layout, {
|
||||
x: defaultX,
|
||||
y: defaultY
|
||||
});
|
||||
data.item_data.position_x = target.x;
|
||||
data.item_data.position_y = target.y;
|
||||
data.position_x = target.x;
|
||||
data.position_y = target.y;
|
||||
void operationCreate({ itemID: oss.id, data: data }).then(response => onCreate?.(response.new_operation.id));
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ export function TabSynthesisOperation() {
|
|||
name='arguments'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<PickMultiOperation items={oss.items} value={field.value} onChange={field.onChange} rows={6} />
|
||||
<PickMultiOperation items={oss.operations} value={field.value} onChange={field.onChange} rows={6} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -9,25 +9,25 @@ import { Checkbox, TextInput } from '@/components/input';
|
|||
import { ModalForm } from '@/components/modal';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { type IOperationDeleteDTO, type IOperationPosition, schemaOperationDelete } from '../backend/types';
|
||||
import { type IOperationDeleteDTO, type IOssLayout, schemaOperationDelete } from '../backend/types';
|
||||
import { useOperationDelete } from '../backend/use-operation-delete';
|
||||
import { type IOperation, type IOperationSchema } from '../models/oss';
|
||||
|
||||
export interface DlgDeleteOperationProps {
|
||||
oss: IOperationSchema;
|
||||
target: IOperation;
|
||||
positions: IOperationPosition[];
|
||||
layout: IOssLayout;
|
||||
}
|
||||
|
||||
export function DlgDeleteOperation() {
|
||||
const { oss, target, positions } = useDialogsStore(state => state.props as DlgDeleteOperationProps);
|
||||
const { oss, target, layout } = useDialogsStore(state => state.props as DlgDeleteOperationProps);
|
||||
const { operationDelete } = useOperationDelete();
|
||||
|
||||
const { handleSubmit, control } = useForm<IOperationDeleteDTO>({
|
||||
resolver: zodResolver(schemaOperationDelete),
|
||||
defaultValues: {
|
||||
target: target.id,
|
||||
positions: positions,
|
||||
layout: layout,
|
||||
keep_constituents: false,
|
||||
delete_schema: false
|
||||
}
|
||||
|
|
|
@ -11,12 +11,7 @@ import { ModalForm } from '@/components/modal';
|
|||
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import {
|
||||
type IOperationPosition,
|
||||
type IOperationUpdateDTO,
|
||||
OperationType,
|
||||
schemaOperationUpdate
|
||||
} from '../../backend/types';
|
||||
import { type IOperationUpdateDTO, type IOssLayout, OperationType, schemaOperationUpdate } from '../../backend/types';
|
||||
import { useOperationUpdate } from '../../backend/use-operation-update';
|
||||
import { type IOperation, type IOperationSchema } from '../../models/oss';
|
||||
|
||||
|
@ -27,7 +22,7 @@ import { TabSynthesis } from './tab-synthesis';
|
|||
export interface DlgEditOperationProps {
|
||||
oss: IOperationSchema;
|
||||
target: IOperation;
|
||||
positions: IOperationPosition[];
|
||||
layout: IOssLayout;
|
||||
}
|
||||
|
||||
export const TabID = {
|
||||
|
@ -38,7 +33,7 @@ export const TabID = {
|
|||
export type TabID = (typeof TabID)[keyof typeof TabID];
|
||||
|
||||
export function DlgEditOperation() {
|
||||
const { oss, target, positions } = useDialogsStore(state => state.props as DlgEditOperationProps);
|
||||
const { oss, target, layout } = useDialogsStore(state => state.props as DlgEditOperationProps);
|
||||
const { operationUpdate } = useOperationUpdate();
|
||||
|
||||
const methods = useForm<IOperationUpdateDTO>({
|
||||
|
@ -55,7 +50,7 @@ export function DlgEditOperation() {
|
|||
original: sub.original,
|
||||
substitution: sub.substitution
|
||||
})),
|
||||
positions: positions
|
||||
layout: layout
|
||||
},
|
||||
mode: 'onChange'
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@ export function TabArguments() {
|
|||
const { control, setValue } = useFormContext<IOperationUpdateDTO>();
|
||||
const { oss, target } = useDialogsStore(state => state.props as DlgEditOperationProps);
|
||||
const potentialCycle = [target.id, ...oss.graph.expandAllOutputs([target.id])];
|
||||
const filtered = oss.items.filter(item => !potentialCycle.includes(item.id));
|
||||
const filtered = oss.operations.filter(item => !potentialCycle.includes(item.id));
|
||||
|
||||
function handleChangeArguments(prev: number[], newValue: number[]) {
|
||||
setValue('arguments', newValue, { shouldValidate: true });
|
||||
|
|
|
@ -16,9 +16,9 @@ import { Loader } from '@/components/loader';
|
|||
import { ModalForm } from '@/components/modal';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { type ICstRelocateDTO, type IOperationPosition, schemaCstRelocate } from '../backend/types';
|
||||
import { type ICstRelocateDTO, type IOssLayout, schemaCstRelocate } from '../backend/types';
|
||||
import { useRelocateConstituents } from '../backend/use-relocate-constituents';
|
||||
import { useUpdatePositions } from '../backend/use-update-positions';
|
||||
import { useUpdateLayout } from '../backend/use-update-layout';
|
||||
import { IconRelocationUp } from '../components/icon-relocation-up';
|
||||
import { type IOperation, type IOperationSchema } from '../models/oss';
|
||||
import { getRelocateCandidates } from '../models/oss-api';
|
||||
|
@ -26,13 +26,13 @@ import { getRelocateCandidates } from '../models/oss-api';
|
|||
export interface DlgRelocateConstituentsProps {
|
||||
oss: IOperationSchema;
|
||||
initialTarget?: IOperation;
|
||||
positions: IOperationPosition[];
|
||||
layout?: IOssLayout;
|
||||
}
|
||||
|
||||
export function DlgRelocateConstituents() {
|
||||
const { oss, initialTarget, positions } = useDialogsStore(state => state.props as DlgRelocateConstituentsProps);
|
||||
const { oss, initialTarget, layout } = useDialogsStore(state => state.props as DlgRelocateConstituentsProps);
|
||||
const { items: libraryItems } = useLibrary();
|
||||
const { updatePositions } = useUpdatePositions();
|
||||
const { updateLayout: updatePositions } = useUpdateLayout();
|
||||
const { relocateConstituents } = useRelocateConstituents();
|
||||
|
||||
const {
|
||||
|
@ -55,7 +55,7 @@ export function DlgRelocateConstituents() {
|
|||
libraryItems.find(item => item.id === initialTarget?.result) ?? null
|
||||
);
|
||||
|
||||
const operation = oss.items.find(item => item.result === source?.id);
|
||||
const operation = oss.operations.find(item => item.result === source?.id);
|
||||
const sourceSchemas = libraryItems.filter(item => oss.schemas.includes(item.id));
|
||||
const destinationSchemas = (() => {
|
||||
if (!operation) {
|
||||
|
@ -73,7 +73,7 @@ export function DlgRelocateConstituents() {
|
|||
if (!sourceData.schema || !destinationItem || !operation) {
|
||||
return [];
|
||||
}
|
||||
const destinationOperation = oss.items.find(item => item.result === destination);
|
||||
const destinationOperation = oss.operations.find(item => item.result === destination);
|
||||
return getRelocateCandidates(operation.id, destinationOperation!.id, sourceData.schema, oss);
|
||||
})();
|
||||
|
||||
|
@ -98,17 +98,13 @@ export function DlgRelocateConstituents() {
|
|||
}
|
||||
|
||||
function onSubmit(data: ICstRelocateDTO) {
|
||||
const positionsUnchanged = positions.every(item => {
|
||||
const operation = oss.operationByID.get(item.id)!;
|
||||
return operation.position_x === item.position_x && operation.position_y === item.position_y;
|
||||
});
|
||||
if (positionsUnchanged) {
|
||||
if (!layout || JSON.stringify(layout) === JSON.stringify(oss.layout)) {
|
||||
return relocateConstituents(data);
|
||||
} else {
|
||||
return updatePositions({
|
||||
isSilent: true,
|
||||
itemID: oss.id,
|
||||
positions: positions
|
||||
data: layout
|
||||
}).then(() => relocateConstituents(data));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import { infoMsg } from '@/utils/labels';
|
|||
import { TextMatcher } from '@/utils/utils';
|
||||
|
||||
import { Graph } from '../../../models/graph';
|
||||
import { type IOperationPosition } from '../backend/types';
|
||||
import { type IOssLayout } from '../backend/types';
|
||||
import { describeSubstitutionError } from '../labels';
|
||||
|
||||
import { type IOperation, type IOperationSchema, SubstitutionErrorType } from './oss';
|
||||
|
@ -494,40 +494,39 @@ export function getRelocateCandidates(
|
|||
export function calculateInsertPosition(
|
||||
oss: IOperationSchema,
|
||||
argumentsOps: number[],
|
||||
positions: IOperationPosition[],
|
||||
layout: IOssLayout,
|
||||
defaultPosition: Position2D
|
||||
): Position2D {
|
||||
const result = defaultPosition;
|
||||
if (positions.length === 0) {
|
||||
const operations = layout.operations;
|
||||
if (operations.length === 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (argumentsOps.length === 0) {
|
||||
let inputsPositions = positions.filter(pos =>
|
||||
oss.items.find(operation => operation.arguments.length === 0 && operation.id === pos.id)
|
||||
let inputsPositions = operations.filter(pos =>
|
||||
oss.operations.find(operation => operation.arguments.length === 0 && operation.id === pos.id)
|
||||
);
|
||||
if (inputsPositions.length === 0) {
|
||||
inputsPositions = positions;
|
||||
inputsPositions = operations;
|
||||
}
|
||||
const maxX = Math.max(...inputsPositions.map(node => node.position_x));
|
||||
const minY = Math.min(...inputsPositions.map(node => node.position_y));
|
||||
const maxX = Math.max(...inputsPositions.map(node => node.x));
|
||||
const minY = Math.min(...inputsPositions.map(node => node.y));
|
||||
result.x = maxX + DISTANCE_X;
|
||||
result.y = minY;
|
||||
} else {
|
||||
const argNodes = positions.filter(pos => argumentsOps.includes(pos.id));
|
||||
const maxY = Math.max(...argNodes.map(node => node.position_y));
|
||||
const minX = Math.min(...argNodes.map(node => node.position_x));
|
||||
const maxX = Math.max(...argNodes.map(node => node.position_x));
|
||||
const argNodes = operations.filter(pos => argumentsOps.includes(pos.id));
|
||||
const maxY = Math.max(...argNodes.map(node => node.y));
|
||||
const minX = Math.min(...argNodes.map(node => node.x));
|
||||
const maxX = Math.max(...argNodes.map(node => node.x));
|
||||
result.x = Math.ceil((maxX + minX) / 2 / GRID_SIZE) * GRID_SIZE;
|
||||
result.y = maxY + DISTANCE_Y;
|
||||
}
|
||||
|
||||
let flagIntersect = false;
|
||||
do {
|
||||
flagIntersect = positions.some(
|
||||
position =>
|
||||
Math.abs(position.position_x - result.x) < MIN_DISTANCE &&
|
||||
Math.abs(position.position_y - result.y) < MIN_DISTANCE
|
||||
flagIntersect = operations.some(
|
||||
position => Math.abs(position.x - result.x) < MIN_DISTANCE && Math.abs(position.y - result.y) < MIN_DISTANCE
|
||||
);
|
||||
if (flagIntersect) {
|
||||
result.x += MIN_DISTANCE;
|
||||
|
|
|
@ -8,6 +8,8 @@ import { type ICstSubstituteInfo, type IOperationDTO, type IOperationSchemaDTO }
|
|||
|
||||
/** Represents Operation. */
|
||||
export interface IOperation extends IOperationDTO {
|
||||
x: number;
|
||||
y: number;
|
||||
is_owned: boolean;
|
||||
is_consolidation: boolean; // aka 'diamond synthesis'
|
||||
substitutions: ICstSubstituteInfo[];
|
||||
|
@ -25,7 +27,7 @@ export interface IOperationSchemaStats {
|
|||
|
||||
/** Represents OperationSchema. */
|
||||
export interface IOperationSchema extends IOperationSchemaDTO {
|
||||
items: IOperation[];
|
||||
operations: IOperation[];
|
||||
|
||||
graph: Graph;
|
||||
schemas: number[];
|
||||
|
|
|
@ -27,7 +27,7 @@ import { useMutatingOss } from '../../../backend/use-mutating-oss';
|
|||
import { type IOperation } from '../../../models/oss';
|
||||
import { useOssEdit } from '../oss-edit-context';
|
||||
|
||||
import { useGetPositions } from './use-get-positions';
|
||||
import { useGetLayout } from './use-get-layout';
|
||||
|
||||
// pixels - size of OSS context menu
|
||||
const MENU_WIDTH = 200;
|
||||
|
@ -49,7 +49,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }:
|
|||
const { items: libraryItems } = useLibrary();
|
||||
const { schema, navigateOperationSchema, isMutable, canDeleteOperation: canDelete } = useOssEdit();
|
||||
const isProcessing = useMutatingOss();
|
||||
const getPositions = useGetPositions();
|
||||
const getLayout = useGetLayout();
|
||||
|
||||
const { inputCreate } = useInputCreate();
|
||||
const { operationExecute } = useOperationExecute();
|
||||
|
@ -104,7 +104,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }:
|
|||
showEditInput({
|
||||
oss: schema,
|
||||
target: operation,
|
||||
positions: getPositions()
|
||||
layout: getLayout()
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }:
|
|||
showEditOperation({
|
||||
oss: schema,
|
||||
target: operation,
|
||||
positions: getPositions()
|
||||
layout: getLayout()
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -128,7 +128,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }:
|
|||
showDeleteOperation({
|
||||
oss: schema,
|
||||
target: operation,
|
||||
positions: getPositions()
|
||||
layout: getLayout()
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }:
|
|||
onHide();
|
||||
void operationExecute({
|
||||
itemID: schema.id, //
|
||||
data: { target: operation.id, positions: getPositions() }
|
||||
data: { target: operation.id, layout: getLayout() }
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -154,7 +154,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }:
|
|||
onHide();
|
||||
void inputCreate({
|
||||
itemID: schema.id,
|
||||
data: { target: operation.id, positions: getPositions() }
|
||||
data: { target: operation.id, layout: getLayout() }
|
||||
}).then(new_schema => router.push({ path: urls.schema(new_schema.id), force: true }));
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }:
|
|||
showRelocateConstituents({
|
||||
oss: schema,
|
||||
initialTarget: operation,
|
||||
positions: getPositions()
|
||||
layout: getLayout()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import { useDialogsStore } from '@/stores/dialogs';
|
|||
import { PARAMETER } from '@/utils/constants';
|
||||
|
||||
import { useMutatingOss } from '../../../backend/use-mutating-oss';
|
||||
import { useUpdatePositions } from '../../../backend/use-update-positions';
|
||||
import { useUpdateLayout } from '../../../backend/use-update-layout';
|
||||
import { GRID_SIZE } from '../../../models/oss-api';
|
||||
import { type OssNode } from '../../../models/oss-layout';
|
||||
import { useOperationTooltipStore } from '../../../stores/operation-tooltip';
|
||||
|
@ -26,7 +26,7 @@ import { useOssEdit } from '../oss-edit-context';
|
|||
import { OssNodeTypes } from './graph/oss-node-types';
|
||||
import { type ContextMenuData, NodeContextMenu } from './node-context-menu';
|
||||
import { ToolbarOssGraph } from './toolbar-oss-graph';
|
||||
import { useGetPositions } from './use-get-positions';
|
||||
import { useGetLayout } from './use-get-layout';
|
||||
|
||||
const ZOOM_MAX = 2;
|
||||
const ZOOM_MIN = 0.5;
|
||||
|
@ -52,8 +52,8 @@ export function OssFlow() {
|
|||
const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate);
|
||||
const edgeStraight = useOSSGraphStore(state => state.edgeStraight);
|
||||
|
||||
const getPositions = useGetPositions();
|
||||
const { updatePositions } = useUpdatePositions();
|
||||
const getLayout = useGetLayout();
|
||||
const { updateLayout: updatePositions } = useUpdateLayout();
|
||||
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
@ -78,10 +78,10 @@ export function OssFlow() {
|
|||
|
||||
useEffect(() => {
|
||||
setNodes(
|
||||
schema.items.map(operation => ({
|
||||
schema.operations.map(operation => ({
|
||||
id: String(operation.id),
|
||||
data: { label: operation.alias, operation: operation },
|
||||
position: { x: operation.position_x, y: operation.position_y },
|
||||
position: { x: operation.x, y: operation.y },
|
||||
type: operation.operation_type.toString()
|
||||
}))
|
||||
);
|
||||
|
@ -93,8 +93,7 @@ export function OssFlow() {
|
|||
type: edgeStraight ? 'straight' : 'simplebezier',
|
||||
animated: edgeAnimate,
|
||||
targetHandle:
|
||||
schema.operationByID.get(argument.argument)!.position_x >
|
||||
schema.operationByID.get(argument.operation)!.position_x
|
||||
schema.operationByID.get(argument.argument)!.x > schema.operationByID.get(argument.operation)!.x
|
||||
? 'right'
|
||||
: 'left'
|
||||
}))
|
||||
|
@ -103,16 +102,7 @@ export function OssFlow() {
|
|||
}, [schema, setNodes, setEdges, toggleReset, edgeStraight, edgeAnimate, fitView]);
|
||||
|
||||
function handleSavePositions() {
|
||||
const positions = getPositions();
|
||||
void updatePositions({ itemID: schema.id, positions: positions }).then(() => {
|
||||
positions.forEach(item => {
|
||||
const operation = schema.operationByID.get(item.id);
|
||||
if (operation) {
|
||||
operation.position_x = item.position_x;
|
||||
operation.position_y = item.position_y;
|
||||
}
|
||||
});
|
||||
});
|
||||
void updatePositions({ itemID: schema.id, data: getLayout() });
|
||||
}
|
||||
|
||||
function handleCreateOperation() {
|
||||
|
@ -121,7 +111,7 @@ export function OssFlow() {
|
|||
oss: schema,
|
||||
defaultX: targetPosition.x,
|
||||
defaultY: targetPosition.y,
|
||||
positions: getPositions(),
|
||||
layout: getLayout(),
|
||||
initialInputs: selected,
|
||||
onCreate: () =>
|
||||
setTimeout(() => fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING }), PARAMETER.refreshTimeout)
|
||||
|
@ -139,7 +129,7 @@ export function OssFlow() {
|
|||
showDeleteOperation({
|
||||
oss: schema,
|
||||
target: operation,
|
||||
positions: getPositions()
|
||||
layout: getLayout()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import clsx from 'clsx';
|
|||
import { HelpTopic } from '@/features/help';
|
||||
import { BadgeHelp } from '@/features/help/components';
|
||||
import { useOperationExecute } from '@/features/oss/backend/use-operation-execute';
|
||||
import { useUpdatePositions } from '@/features/oss/backend/use-update-positions';
|
||||
import { useUpdateLayout } from '@/features/oss/backend/use-update-layout';
|
||||
|
||||
import { MiniButton } from '@/components/control';
|
||||
import {
|
||||
|
@ -34,7 +34,7 @@ import { useOSSGraphStore } from '../../../stores/oss-graph';
|
|||
import { useOssEdit } from '../oss-edit-context';
|
||||
|
||||
import { VIEW_PADDING } from './oss-flow';
|
||||
import { useGetPositions } from './use-get-positions';
|
||||
import { useGetLayout } from './use-get-layout';
|
||||
|
||||
interface ToolbarOssGraphProps extends Styling {
|
||||
onCreate: () => void;
|
||||
|
@ -53,7 +53,7 @@ export function ToolbarOssGraph({
|
|||
const isProcessing = useMutatingOss();
|
||||
const { fitView } = useReactFlow();
|
||||
const selectedOperation = schema.operationByID.get(selected[0]);
|
||||
const getPositions = useGetPositions();
|
||||
const getLayout = useGetLayout();
|
||||
|
||||
const showGrid = useOSSGraphStore(state => state.showGrid);
|
||||
const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate);
|
||||
|
@ -62,7 +62,7 @@ export function ToolbarOssGraph({
|
|||
const toggleEdgeAnimate = useOSSGraphStore(state => state.toggleEdgeAnimate);
|
||||
const toggleEdgeStraight = useOSSGraphStore(state => state.toggleEdgeStraight);
|
||||
|
||||
const { updatePositions } = useUpdatePositions();
|
||||
const { updateLayout: updatePositions } = useUpdateLayout();
|
||||
const { operationExecute } = useOperationExecute();
|
||||
|
||||
const showEditOperation = useDialogsStore(state => state.showEditOperation);
|
||||
|
@ -93,16 +93,7 @@ export function ToolbarOssGraph({
|
|||
}
|
||||
|
||||
function handleSavePositions() {
|
||||
const positions = getPositions();
|
||||
void updatePositions({ itemID: schema.id, positions: positions }).then(() => {
|
||||
positions.forEach(item => {
|
||||
const operation = schema.operationByID.get(item.id);
|
||||
if (operation) {
|
||||
operation.position_x = item.position_x;
|
||||
operation.position_y = item.position_y;
|
||||
}
|
||||
});
|
||||
});
|
||||
void updatePositions({ itemID: schema.id, data: getLayout() });
|
||||
}
|
||||
|
||||
function handleOperationExecute() {
|
||||
|
@ -111,7 +102,7 @@ export function ToolbarOssGraph({
|
|||
}
|
||||
void operationExecute({
|
||||
itemID: schema.id, //
|
||||
data: { target: selectedOperation.id, positions: getPositions() }
|
||||
data: { target: selectedOperation.id, layout: getLayout() }
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -122,7 +113,7 @@ export function ToolbarOssGraph({
|
|||
showEditOperation({
|
||||
oss: schema,
|
||||
target: selectedOperation,
|
||||
positions: getPositions()
|
||||
layout: getLayout()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { useReactFlow } from 'reactflow';
|
||||
|
||||
import { type IOssLayout } from '@/features/oss/backend/types';
|
||||
|
||||
export function useGetLayout() {
|
||||
const { getNodes } = useReactFlow();
|
||||
return function getLayout(): IOssLayout {
|
||||
return {
|
||||
operations: getNodes().map(node => ({
|
||||
id: Number(node.id),
|
||||
x: node.position.x,
|
||||
y: node.position.y
|
||||
})),
|
||||
blocks: []
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { useReactFlow } from 'reactflow';
|
||||
|
||||
export function useGetPositions() {
|
||||
const { getNodes } = useReactFlow();
|
||||
return function getPositions() {
|
||||
return getNodes().map(node => ({
|
||||
id: Number(node.id),
|
||||
position_x: node.position.x,
|
||||
position_y: node.position.y
|
||||
}));
|
||||
};
|
||||
}
|
|
@ -21,8 +21,7 @@ export function MenuEditOss() {
|
|||
menu.hide();
|
||||
showRelocateConstituents({
|
||||
oss: schema,
|
||||
initialTarget: undefined,
|
||||
positions: []
|
||||
initialTarget: undefined
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user