From 077b9d221679042540935011683ba85facc00114 Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Thu, 1 Aug 2024 21:16:40 +0300 Subject: [PATCH] F: Add function for predecessor tracing --- .../backend/apps/oss/serializers/__init__.py | 2 +- .../backend/apps/oss/serializers/responses.py | 6 + rsconcept/backend/apps/oss/views/oss.py | 35 ++++++ .../apps/rsform/serializers/data_access.py | 18 ++- .../backend/apps/rsform/views/rsforms.py | 4 + rsconcept/frontend/src/backend/oss.ts | 8 ++ rsconcept/frontend/src/components/Icons.tsx | 2 +- .../frontend/src/context/RSFormContext.tsx | 16 +++ rsconcept/frontend/src/models/rsform.ts | 5 + .../EditorConstituenta/EditorConstituenta.tsx | 24 ++-- .../ToolbarConstituenta.tsx | 116 ++++++++++-------- .../src/pages/RSFormPage/RSEditContext.tsx | 18 +++ 12 files changed, 173 insertions(+), 81 deletions(-) diff --git a/rsconcept/backend/apps/oss/serializers/__init__.py b/rsconcept/backend/apps/oss/serializers/__init__.py index d6633769..bb2d7e3d 100644 --- a/rsconcept/backend/apps/oss/serializers/__init__.py +++ b/rsconcept/backend/apps/oss/serializers/__init__.py @@ -10,4 +10,4 @@ from .data_access import ( OperationUpdateSerializer, SetOperationInputSerializer ) -from .responses import NewOperationResponse, NewSchemaResponse +from .responses import ConstituentaReferenceResponse, NewOperationResponse, NewSchemaResponse diff --git a/rsconcept/backend/apps/oss/serializers/responses.py b/rsconcept/backend/apps/oss/serializers/responses.py index 4fd0be31..7dfb9017 100644 --- a/rsconcept/backend/apps/oss/serializers/responses.py +++ b/rsconcept/backend/apps/oss/serializers/responses.py @@ -16,3 +16,9 @@ class NewSchemaResponse(serializers.Serializer): ''' Serializer: Create RSForm for input operation response. ''' new_schema = LibraryItemSerializer() oss = OperationSchemaSerializer() + + +class ConstituentaReferenceResponse(serializers.Serializer): + ''' Serializer: Constituenta reference. ''' + id = serializers.IntegerField() + schema = serializers.IntegerField() diff --git a/rsconcept/backend/apps/oss/views/oss.py b/rsconcept/backend/apps/oss/views/oss.py index 0a07471b..37988d27 100644 --- a/rsconcept/backend/apps/oss/views/oss.py +++ b/rsconcept/backend/apps/oss/views/oss.py @@ -12,6 +12,8 @@ from rest_framework.response import Response from apps.library.models import LibraryItem, LibraryItemType from apps.library.serializers import LibraryItemSerializer +from apps.rsform.models import Constituenta +from apps.rsform.serializers import CstTargetSerializer from shared import messages as msg from shared import permissions @@ -43,6 +45,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev permission_list = [permissions.ItemEditor] elif self.action in ['details']: permission_list = [permissions.ItemAnyone] + elif self.action in ['get_predecessor']: + permission_list = [permissions.Anyone] else: permission_list = [permissions.Anyone] return [permission() for permission in permission_list] @@ -306,3 +310,34 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev status=c.HTTP_200_OK, data=s.OperationSchemaSerializer(oss.model).data ) + + @extend_schema( + summary='get predecessor for target constituenta', + tags=['OSS'], + request=CstTargetSerializer(), + responses={ + c.HTTP_200_OK: s.ConstituentaReferenceResponse, + c.HTTP_400_BAD_REQUEST: None, + c.HTTP_403_FORBIDDEN: None, + c.HTTP_404_NOT_FOUND: None + } + ) + @action(detail=False, methods=['post'], url_path='get-predecessor') + def get_predecessor(self, request: Request): + ''' Get predecessor. ''' + # TODO: add tests for this method + serializer = CstTargetSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + cst = cast(Constituenta, serializer.validated_data['target']) + inheritance = m.Inheritance.objects.filter(child=cst) + while inheritance.exists(): + cst = cast(m.Inheritance, inheritance.first()).parent + inheritance = m.Inheritance.objects.filter(child=cst) + + return Response( + status=c.HTTP_200_OK, + data={ + 'id': cst.pk, + 'schema': cst.schema_id + } + ) diff --git a/rsconcept/backend/apps/rsform/serializers/data_access.py b/rsconcept/backend/apps/rsform/serializers/data_access.py index 77b4e0cb..b34a05c4 100644 --- a/rsconcept/backend/apps/rsform/serializers/data_access.py +++ b/rsconcept/backend/apps/rsform/serializers/data_access.py @@ -231,17 +231,13 @@ class CstTargetSerializer(serializers.Serializer): target = PKField(many=False, queryset=Constituenta.objects.all()) def validate(self, attrs): - schema = cast(LibraryItem, self.context['schema']) - cst = cast(Constituenta, attrs['target']) - if schema and cst.schema_id != schema.pk: - raise serializers.ValidationError({ - f'{cst.pk}': msg.constituentaNotInRSform(schema.title) - }) - if cst.cst_type not in [CstType.FUNCTION, CstType.STRUCTURED, CstType.TERM]: - raise serializers.ValidationError({ - f'{cst.pk}': msg.constituentaNoStructure() - }) - self.instance = cst + if 'schema' in self.context: + schema = cast(LibraryItem, self.context['schema']) + cst = cast(Constituenta, attrs['target']) + if schema and cst.schema_id != schema.pk: + raise serializers.ValidationError({ + f'{cst.pk}': msg.constituentaNotInRSform(schema.title) + }) return attrs diff --git a/rsconcept/backend/apps/rsform/views/rsforms.py b/rsconcept/backend/apps/rsform/views/rsforms.py index 7b99d8b1..e4773c45 100644 --- a/rsconcept/backend/apps/rsform/views/rsforms.py +++ b/rsconcept/backend/apps/rsform/views/rsforms.py @@ -147,6 +147,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr serializer = s.CstTargetSerializer(data=request.data, context={'schema': schema}) serializer.is_valid(raise_exception=True) cst = cast(m.Constituenta, serializer.validated_data['target']) + if cst.cst_type not in [m.CstType.FUNCTION, m.CstType.STRUCTURED, m.CstType.TERM]: + raise ValidationError({ + f'{cst.pk}': msg.constituentaNoStructure() + }) schema_details = s.RSFormParseSerializer(schema).data['items'] cst_parse = next(item for item in schema_details if item['id'] == cst.pk)['parse'] diff --git a/rsconcept/frontend/src/backend/oss.ts b/rsconcept/frontend/src/backend/oss.ts index 05c8a359..da74dc14 100644 --- a/rsconcept/frontend/src/backend/oss.ts +++ b/rsconcept/frontend/src/backend/oss.ts @@ -12,6 +12,7 @@ import { IPositionsData, ITargetOperation } from '@/models/oss'; +import { IConstituentaReference, ITargetCst } from '@/models/rsform'; import { AxiosGet, AxiosPatch, AxiosPost, FrontExchange, FrontPull, FrontPush } from './apiTransport'; @@ -73,3 +74,10 @@ export function postExecuteOperation(oss: string, request: FrontExchange) { + AxiosPost({ + endpoint: `/api/oss/get-predecessor`, + request: request + }); +} diff --git a/rsconcept/frontend/src/components/Icons.tsx b/rsconcept/frontend/src/components/Icons.tsx index f35f6252..370ad589 100644 --- a/rsconcept/frontend/src/components/Icons.tsx +++ b/rsconcept/frontend/src/components/Icons.tsx @@ -64,7 +64,7 @@ export { BiDiamond as IconTemplates } from 'react-icons/bi'; export { GiHoneycomb as IconOSS } from 'react-icons/gi'; export { LuBaby as IconChild } from 'react-icons/lu'; export { RiParentLine as IconParent } from 'react-icons/ri'; -export { TbOld as IconPredecessor } from 'react-icons/tb'; +export { BiSpa as IconPredecessor } from 'react-icons/bi'; export { RiHexagonLine as IconRSForm } from 'react-icons/ri'; export { LuArchive as IconArchive } from 'react-icons/lu'; export { LuDatabase as IconDatabase } from 'react-icons/lu'; diff --git a/rsconcept/frontend/src/context/RSFormContext.tsx b/rsconcept/frontend/src/context/RSFormContext.tsx index db985e23..fe0427bb 100644 --- a/rsconcept/frontend/src/context/RSFormContext.tsx +++ b/rsconcept/frontend/src/context/RSFormContext.tsx @@ -13,6 +13,7 @@ import { postCreateVersion, postSubscribe } from '@/backend/library'; +import { postFindPredecessor } from '@/backend/oss'; import { getTRSFile, patchDeleteConstituenta, @@ -37,6 +38,7 @@ import { ConstituentaID, IConstituentaList, IConstituentaMeta, + IConstituentaReference, ICstCreateData, ICstMovetoData, ICstRenameData, @@ -89,6 +91,7 @@ interface IRSFormContext { cstUpdate: (data: ICstUpdateData, callback?: DataCallback) => void; cstDelete: (data: IConstituentaList, callback?: () => void) => void; cstMoveTo: (data: ICstMovetoData, callback?: () => void) => void; + findPredecessor: (data: ITargetCst, callback: (reference: IConstituentaReference) => void) => void; versionCreate: (data: IVersionData, callback?: (version: VersionID) => void) => void; versionUpdate: (target: VersionID, data: IVersionData, callback?: () => void) => void; @@ -526,6 +529,17 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) = [itemID, library, setSchema] ); + const findPredecessor = useCallback((data: ITargetCst, callback: (reference: IConstituentaReference) => void) => { + setProcessingError(undefined); + postFindPredecessor({ + data: data, + showError: true, + setLoading: setProcessing, + onError: setProcessingError, + onSuccess: callback + }); + }, []); + const versionUpdate = useCallback( (target: number, data: IVersionData, callback?: () => void) => { setProcessingError(undefined); @@ -638,6 +652,8 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) = cstSubstitute, cstDelete, cstMoveTo, + findPredecessor, + versionCreate, versionUpdate, versionDelete, diff --git a/rsconcept/frontend/src/models/rsform.ts b/rsconcept/frontend/src/models/rsform.ts index 7778f525..b81ef9c2 100644 --- a/rsconcept/frontend/src/models/rsform.ts +++ b/rsconcept/frontend/src/models/rsform.ts @@ -119,6 +119,11 @@ export interface IConstituenta extends IConstituentaData { children_alias: string[]; } +/** + * Represents {@link IConstituenta} reference. + */ +export interface IConstituentaReference extends Pick {} + /** * Represents Constituenta list. */ diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx index 097db066..c9775724 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx @@ -80,21 +80,15 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit } return (
- {controller.isContentEditable ? ( - setToggleReset(prev => !prev)} - onDelete={controller.promptDeleteCst} - onClone={controller.cloneCst} - onCreate={() => controller.createCst(activeCst?.cst_type, false)} - onToggleList={() => setShowList(prev => !prev)} - /> - ) : null} + setToggleReset(prev => !prev)} + onToggleList={() => setShowList(prev => !prev)} + />
void; onReset: () => void; - - onMoveUp: () => void; - onMoveDown: () => void; - onDelete: () => void; - onClone: () => void; - onCreate: () => void; onToggleList: () => void; } function ToolbarConstituenta({ + activeCst, disabled, modified, showList, onSubmit, onReset, - onMoveUp, - onMoveDown, - onDelete, - onClone, - onCreate, onToggleList }: ToolbarConstituentaProps) { const controller = useRSEdit(); @@ -61,53 +54,70 @@ function ToolbarConstituenta({ onSelect={(event, value) => controller.viewOSS(value.id, event.ctrlKey || event.metaKey)} /> ) : null} - } - disabled={disabled || !modified} - onClick={onSubmit} - /> - } - disabled={disabled || !modified} - onClick={onReset} - /> - } - disabled={disabled} - onClick={onCreate} - /> - } - disabled={disabled || modified} - onClick={onClone} - /> - } - /> + {activeCst && activeCst.is_inherited ? ( + controller.viewPredecessor(activeCst.id)} + icon={} + /> + ) : null} + {controller.isContentEditable ? ( + <> + } + disabled={disabled || !modified} + onClick={onSubmit} + /> + } + disabled={disabled || !modified} + onClick={onReset} + /> + } + disabled={disabled} + onClick={() => controller.createCst(activeCst?.cst_type, false)} + /> + } + disabled={disabled || modified} + onClick={controller.cloneCst} + /> + } + /> + + ) : null} + : } onClick={onToggleList} /> - } - disabled={disabled || modified} - onClick={onMoveUp} - /> - } - disabled={disabled || modified} - onClick={onMoveDown} - /> + + {controller.isContentEditable ? ( + <> + } + disabled={disabled || modified} + onClick={controller.moveUp} + /> + } + disabled={disabled || modified} + onClick={controller.moveDown} + /> + + ) : null} ); diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx index 557969f7..d2a21970 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx @@ -53,6 +53,8 @@ import { EXTEOR_TRS_FILE } from '@/utils/constants'; import { information, prompts } from '@/utils/labels'; import { promptUnsaved } from '@/utils/utils'; +import { RSTabID } from './RSTabs'; + export interface IRSEditContext { schema?: IRSForm; selected: ConstituentaID[]; @@ -80,6 +82,7 @@ export interface IRSEditContext { viewOSS: (target: LibraryItemID, newTab?: boolean) => void; viewVersion: (version?: VersionID, newTab?: boolean) => void; + viewPredecessor: (target: ConstituentaID) => void; createVersion: () => void; restoreVersion: () => void; promptEditVersions: () => void; @@ -202,6 +205,20 @@ export const RSEditState = ({ [router, model] ); + const viewPredecessor = useCallback( + (target: ConstituentaID) => + model.findPredecessor({ target: target }, reference => + router.push( + urls.schema_props({ + id: reference.schema, + active: reference.id, + tab: RSTabID.CST_EDIT + }) + ) + ), + [router, model] + ); + const viewOSS = useCallback( (target: LibraryItemID, newTab?: boolean) => router.push(urls.oss(target), newTab), [router] @@ -620,6 +637,7 @@ export const RSEditState = ({ viewOSS, viewVersion, + viewPredecessor, createVersion, restoreVersion, promptEditVersions: () => setShowEditVersions(true),