F: Add function for predecessor tracing
Some checks failed
Backend CI / build (3.12) (push) Has been cancelled
Frontend CI / build (22.x) (push) Has been cancelled

Update oss.py
This commit is contained in:
Ivan 2024-08-01 21:16:26 +03:00
parent e29f7409c1
commit 27ba4a5da8
12 changed files with 173 additions and 81 deletions

View File

@ -10,4 +10,4 @@ from .data_access import (
OperationUpdateSerializer, OperationUpdateSerializer,
SetOperationInputSerializer SetOperationInputSerializer
) )
from .responses import NewOperationResponse, NewSchemaResponse from .responses import ConstituentaReferenceResponse, NewOperationResponse, NewSchemaResponse

View File

@ -16,3 +16,9 @@ class NewSchemaResponse(serializers.Serializer):
''' Serializer: Create RSForm for input operation response. ''' ''' Serializer: Create RSForm for input operation response. '''
new_schema = LibraryItemSerializer() new_schema = LibraryItemSerializer()
oss = OperationSchemaSerializer() oss = OperationSchemaSerializer()
class ConstituentaReferenceResponse(serializers.Serializer):
''' Serializer: Constituenta reference. '''
id = serializers.IntegerField()
schema = serializers.IntegerField()

View File

@ -12,6 +12,8 @@ from rest_framework.response import Response
from apps.library.models import LibraryItem, LibraryItemType from apps.library.models import LibraryItem, LibraryItemType
from apps.library.serializers import LibraryItemSerializer 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 messages as msg
from shared import permissions from shared import permissions
@ -43,6 +45,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
permission_list = [permissions.ItemEditor] permission_list = [permissions.ItemEditor]
elif self.action in ['details']: elif self.action in ['details']:
permission_list = [permissions.ItemAnyone] permission_list = [permissions.ItemAnyone]
elif self.action in ['get_predecessor']:
permission_list = [permissions.Anyone]
else: else:
permission_list = [permissions.Anyone] permission_list = [permissions.Anyone]
return [permission() for permission in permission_list] return [permission() for permission in permission_list]
@ -306,3 +310,34 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data 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
}
)

View File

@ -231,17 +231,13 @@ class CstTargetSerializer(serializers.Serializer):
target = PKField(many=False, queryset=Constituenta.objects.all()) target = PKField(many=False, queryset=Constituenta.objects.all())
def validate(self, attrs): def validate(self, attrs):
if 'schema' in self.context:
schema = cast(LibraryItem, self.context['schema']) schema = cast(LibraryItem, self.context['schema'])
cst = cast(Constituenta, attrs['target']) cst = cast(Constituenta, attrs['target'])
if schema and cst.schema_id != schema.pk: if schema and cst.schema_id != schema.pk:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{cst.pk}': msg.constituentaNotInRSform(schema.title) 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
return attrs return attrs

View File

@ -147,6 +147,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
serializer = s.CstTargetSerializer(data=request.data, context={'schema': schema}) serializer = s.CstTargetSerializer(data=request.data, context={'schema': schema})
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
cst = cast(m.Constituenta, serializer.validated_data['target']) 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'] schema_details = s.RSFormParseSerializer(schema).data['items']
cst_parse = next(item for item in schema_details if item['id'] == cst.pk)['parse'] cst_parse = next(item for item in schema_details if item['id'] == cst.pk)['parse']

View File

@ -12,6 +12,7 @@ import {
IPositionsData, IPositionsData,
ITargetOperation ITargetOperation
} from '@/models/oss'; } from '@/models/oss';
import { IConstituentaReference, ITargetCst } from '@/models/rsform';
import { AxiosGet, AxiosPatch, AxiosPost, FrontExchange, FrontPull, FrontPush } from './apiTransport'; import { AxiosGet, AxiosPatch, AxiosPost, FrontExchange, FrontPull, FrontPush } from './apiTransport';
@ -73,3 +74,10 @@ export function postExecuteOperation(oss: string, request: FrontExchange<ITarget
request: request request: request
}); });
} }
export function postFindPredecessor(request: FrontExchange<ITargetCst, IConstituentaReference>) {
AxiosPost({
endpoint: `/api/oss/get-predecessor`,
request: request
});
}

View File

@ -64,7 +64,7 @@ export { BiDiamond as IconTemplates } from 'react-icons/bi';
export { GiHoneycomb as IconOSS } from 'react-icons/gi'; export { GiHoneycomb as IconOSS } from 'react-icons/gi';
export { LuBaby as IconChild } from 'react-icons/lu'; export { LuBaby as IconChild } from 'react-icons/lu';
export { RiParentLine as IconParent } from 'react-icons/ri'; 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 { RiHexagonLine as IconRSForm } from 'react-icons/ri';
export { LuArchive as IconArchive } from 'react-icons/lu'; export { LuArchive as IconArchive } from 'react-icons/lu';
export { LuDatabase as IconDatabase } from 'react-icons/lu'; export { LuDatabase as IconDatabase } from 'react-icons/lu';

View File

@ -13,6 +13,7 @@ import {
postCreateVersion, postCreateVersion,
postSubscribe postSubscribe
} from '@/backend/library'; } from '@/backend/library';
import { postFindPredecessor } from '@/backend/oss';
import { import {
getTRSFile, getTRSFile,
patchDeleteConstituenta, patchDeleteConstituenta,
@ -37,6 +38,7 @@ import {
ConstituentaID, ConstituentaID,
IConstituentaList, IConstituentaList,
IConstituentaMeta, IConstituentaMeta,
IConstituentaReference,
ICstCreateData, ICstCreateData,
ICstMovetoData, ICstMovetoData,
ICstRenameData, ICstRenameData,
@ -89,6 +91,7 @@ interface IRSFormContext {
cstUpdate: (data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => void; cstUpdate: (data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => void;
cstDelete: (data: IConstituentaList, callback?: () => void) => void; cstDelete: (data: IConstituentaList, callback?: () => void) => void;
cstMoveTo: (data: ICstMovetoData, 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; versionCreate: (data: IVersionData, callback?: (version: VersionID) => void) => void;
versionUpdate: (target: VersionID, data: IVersionData, callback?: () => void) => void; versionUpdate: (target: VersionID, data: IVersionData, callback?: () => void) => void;
@ -526,6 +529,17 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
[itemID, library, setSchema] [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( const versionUpdate = useCallback(
(target: number, data: IVersionData, callback?: () => void) => { (target: number, data: IVersionData, callback?: () => void) => {
setProcessingError(undefined); setProcessingError(undefined);
@ -638,6 +652,8 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
cstSubstitute, cstSubstitute,
cstDelete, cstDelete,
cstMoveTo, cstMoveTo,
findPredecessor,
versionCreate, versionCreate,
versionUpdate, versionUpdate,
versionDelete, versionDelete,

View File

@ -119,6 +119,11 @@ export interface IConstituenta extends IConstituentaData {
children_alias: string[]; children_alias: string[];
} }
/**
* Represents {@link IConstituenta} reference.
*/
export interface IConstituentaReference extends Pick<IConstituentaMeta, 'id' | 'schema'> {}
/** /**
* Represents Constituenta list. * Represents Constituenta list.
*/ */

View File

@ -80,21 +80,15 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
return ( return (
<div className='overflow-y-auto' style={{ maxHeight: panelHeight }}> <div className='overflow-y-auto' style={{ maxHeight: panelHeight }}>
{controller.isContentEditable ? (
<ToolbarConstituenta <ToolbarConstituenta
activeCst={activeCst}
disabled={disabled} disabled={disabled}
modified={isModified} modified={isModified}
showList={showList} showList={showList}
onMoveUp={controller.moveUp}
onMoveDown={controller.moveDown}
onSubmit={initiateSubmit} onSubmit={initiateSubmit}
onReset={() => setToggleReset(prev => !prev)} onReset={() => setToggleReset(prev => !prev)}
onDelete={controller.promptDeleteCst}
onClone={controller.cloneCst}
onCreate={() => controller.createCst(activeCst?.cst_type, false)}
onToggleList={() => setShowList(prev => !prev)} onToggleList={() => setShowList(prev => !prev)}
/> />
) : null}
<div <div
tabIndex={-1} tabIndex={-1}
className={clsx( className={clsx(

View File

@ -8,6 +8,7 @@ import {
IconMoveDown, IconMoveDown,
IconMoveUp, IconMoveUp,
IconNewItem, IconNewItem,
IconPredecessor,
IconReset, IconReset,
IconSave IconSave
} from '@/components/Icons'; } from '@/components/Icons';
@ -16,39 +17,31 @@ import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { IConstituenta } from '@/models/rsform';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { prepareTooltip, tooltips } from '@/utils/labels'; import { prepareTooltip, tooltips } from '@/utils/labels';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
interface ToolbarConstituentaProps { interface ToolbarConstituentaProps {
activeCst?: IConstituenta;
disabled: boolean; disabled: boolean;
modified: boolean; modified: boolean;
showList: boolean; showList: boolean;
onSubmit: () => void; onSubmit: () => void;
onReset: () => void; onReset: () => void;
onMoveUp: () => void;
onMoveDown: () => void;
onDelete: () => void;
onClone: () => void;
onCreate: () => void;
onToggleList: () => void; onToggleList: () => void;
} }
function ToolbarConstituenta({ function ToolbarConstituenta({
activeCst,
disabled, disabled,
modified, modified,
showList, showList,
onSubmit, onSubmit,
onReset, onReset,
onMoveUp,
onMoveDown,
onDelete,
onClone,
onCreate,
onToggleList onToggleList
}: ToolbarConstituentaProps) { }: ToolbarConstituentaProps) {
const controller = useRSEdit(); const controller = useRSEdit();
@ -61,6 +54,15 @@ function ToolbarConstituenta({
onSelect={(event, value) => controller.viewOSS(value.id, event.ctrlKey || event.metaKey)} onSelect={(event, value) => controller.viewOSS(value.id, event.ctrlKey || event.metaKey)}
/> />
) : null} ) : null}
{activeCst && activeCst.is_inherited ? (
<MiniButton
title='Перейти к исходной конституенте в ОСС'
onClick={() => controller.viewPredecessor(activeCst.id)}
icon={<IconPredecessor size='1.25rem' className='icon-primary' />}
/>
) : null}
{controller.isContentEditable ? (
<>
<MiniButton <MiniButton
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')} titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
icon={<IconSave size='1.25rem' className='icon-primary' />} icon={<IconSave size='1.25rem' className='icon-primary' />}
@ -77,37 +79,45 @@ function ToolbarConstituenta({
title='Создать конституенту после данной' title='Создать конституенту после данной'
icon={<IconNewItem size={'1.25rem'} className='icon-green' />} icon={<IconNewItem size={'1.25rem'} className='icon-green' />}
disabled={disabled} disabled={disabled}
onClick={onCreate} onClick={() => controller.createCst(activeCst?.cst_type, false)}
/> />
<MiniButton <MiniButton
titleHtml={modified ? tooltips.unsaved : prepareTooltip('Клонировать конституенту', 'Alt + V')} titleHtml={modified ? tooltips.unsaved : prepareTooltip('Клонировать конституенту', 'Alt + V')}
icon={<IconClone size='1.25rem' className='icon-green' />} icon={<IconClone size='1.25rem' className='icon-green' />}
disabled={disabled || modified} disabled={disabled || modified}
onClick={onClone} onClick={controller.cloneCst}
/> />
<MiniButton <MiniButton
title='Удалить редактируемую конституенту' title='Удалить редактируемую конституенту'
disabled={disabled || !controller.canDeleteSelected} disabled={disabled || !controller.canDeleteSelected}
onClick={onDelete} onClick={controller.promptDeleteCst}
icon={<IconDestroy size='1.25rem' className='icon-red' />} icon={<IconDestroy size='1.25rem' className='icon-red' />}
/> />
</>
) : null}
<MiniButton <MiniButton
title='Отображение списка конституент' title='Отображение списка конституент'
icon={showList ? <IconList size='1.25rem' className='icon-primary' /> : <IconListOff size='1.25rem' />} icon={showList ? <IconList size='1.25rem' className='icon-primary' /> : <IconListOff size='1.25rem' />}
onClick={onToggleList} onClick={onToggleList}
/> />
{controller.isContentEditable ? (
<>
<MiniButton <MiniButton
titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')} titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')}
icon={<IconMoveUp size='1.25rem' className='icon-primary' />} icon={<IconMoveUp size='1.25rem' className='icon-primary' />}
disabled={disabled || modified} disabled={disabled || modified}
onClick={onMoveUp} onClick={controller.moveUp}
/> />
<MiniButton <MiniButton
titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')} titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')}
icon={<IconMoveDown size='1.25rem' className='icon-primary' />} icon={<IconMoveDown size='1.25rem' className='icon-primary' />}
disabled={disabled || modified} disabled={disabled || modified}
onClick={onMoveDown} onClick={controller.moveDown}
/> />
</>
) : null}
<BadgeHelp topic={HelpTopic.UI_RS_EDITOR} offset={4} className={PARAMETER.TOOLTIP_WIDTH} /> <BadgeHelp topic={HelpTopic.UI_RS_EDITOR} offset={4} className={PARAMETER.TOOLTIP_WIDTH} />
</Overlay> </Overlay>
); );

View File

@ -53,6 +53,8 @@ import { EXTEOR_TRS_FILE } from '@/utils/constants';
import { information, prompts } from '@/utils/labels'; import { information, prompts } from '@/utils/labels';
import { promptUnsaved } from '@/utils/utils'; import { promptUnsaved } from '@/utils/utils';
import { RSTabID } from './RSTabs';
export interface IRSEditContext { export interface IRSEditContext {
schema?: IRSForm; schema?: IRSForm;
selected: ConstituentaID[]; selected: ConstituentaID[];
@ -80,6 +82,7 @@ export interface IRSEditContext {
viewOSS: (target: LibraryItemID, newTab?: boolean) => void; viewOSS: (target: LibraryItemID, newTab?: boolean) => void;
viewVersion: (version?: VersionID, newTab?: boolean) => void; viewVersion: (version?: VersionID, newTab?: boolean) => void;
viewPredecessor: (target: ConstituentaID) => void;
createVersion: () => void; createVersion: () => void;
restoreVersion: () => void; restoreVersion: () => void;
promptEditVersions: () => void; promptEditVersions: () => void;
@ -202,6 +205,20 @@ export const RSEditState = ({
[router, model] [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( const viewOSS = useCallback(
(target: LibraryItemID, newTab?: boolean) => router.push(urls.oss(target), newTab), (target: LibraryItemID, newTab?: boolean) => router.push(urls.oss(target), newTab),
[router] [router]
@ -620,6 +637,7 @@ export const RSEditState = ({
viewOSS, viewOSS,
viewVersion, viewVersion,
viewPredecessor,
createVersion, createVersion,
restoreVersion, restoreVersion,
promptEditVersions: () => setShowEditVersions(true), promptEditVersions: () => setShowEditVersions(true),