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):
schema = cast(LibraryItem, self.context['schema']) if 'schema' in self.context:
cst = cast(Constituenta, attrs['target']) schema = cast(LibraryItem, self.context['schema'])
if schema and cst.schema_id != schema.pk: cst = cast(Constituenta, attrs['target'])
raise serializers.ValidationError({ if schema and cst.schema_id != schema.pk:
f'{cst.pk}': msg.constituentaNotInRSform(schema.title) 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
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} onSubmit={initiateSubmit}
onMoveDown={controller.moveDown} onReset={() => setToggleReset(prev => !prev)}
onSubmit={initiateSubmit} onToggleList={() => setShowList(prev => !prev)}
onReset={() => setToggleReset(prev => !prev)} />
onDelete={controller.promptDeleteCst}
onClone={controller.cloneCst}
onCreate={() => controller.createCst(activeCst?.cst_type, false)}
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,53 +54,70 @@ 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}
<MiniButton {activeCst && activeCst.is_inherited ? (
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')} <MiniButton
icon={<IconSave size='1.25rem' className='icon-primary' />} title='Перейти к исходной конституенте в ОСС'
disabled={disabled || !modified} onClick={() => controller.viewPredecessor(activeCst.id)}
onClick={onSubmit} icon={<IconPredecessor size='1.25rem' className='icon-primary' />}
/> />
<MiniButton ) : null}
title='Сбросить несохраненные изменения' {controller.isContentEditable ? (
icon={<IconReset size='1.25rem' className='icon-primary' />} <>
disabled={disabled || !modified} <MiniButton
onClick={onReset} titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
/> icon={<IconSave size='1.25rem' className='icon-primary' />}
<MiniButton disabled={disabled || !modified}
title='Создать конституенту после данной' onClick={onSubmit}
icon={<IconNewItem size={'1.25rem'} className='icon-green' />} />
disabled={disabled} <MiniButton
onClick={onCreate} title='Сбросить несохраненные изменения'
/> icon={<IconReset size='1.25rem' className='icon-primary' />}
<MiniButton disabled={disabled || !modified}
titleHtml={modified ? tooltips.unsaved : prepareTooltip('Клонировать конституенту', 'Alt + V')} onClick={onReset}
icon={<IconClone size='1.25rem' className='icon-green' />} />
disabled={disabled || modified} <MiniButton
onClick={onClone} title='Создать конституенту после данной'
/> icon={<IconNewItem size={'1.25rem'} className='icon-green' />}
<MiniButton disabled={disabled}
title='Удалить редактируемую конституенту' onClick={() => controller.createCst(activeCst?.cst_type, false)}
disabled={disabled || !controller.canDeleteSelected} />
onClick={onDelete} <MiniButton
icon={<IconDestroy size='1.25rem' className='icon-red' />} titleHtml={modified ? tooltips.unsaved : prepareTooltip('Клонировать конституенту', 'Alt + V')}
/> icon={<IconClone size='1.25rem' className='icon-green' />}
disabled={disabled || modified}
onClick={controller.cloneCst}
/>
<MiniButton
title='Удалить редактируемую конституенту'
disabled={disabled || !controller.canDeleteSelected}
onClick={controller.promptDeleteCst}
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}
/> />
<MiniButton
titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')} {controller.isContentEditable ? (
icon={<IconMoveUp size='1.25rem' className='icon-primary' />} <>
disabled={disabled || modified} <MiniButton
onClick={onMoveUp} titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')}
/> icon={<IconMoveUp size='1.25rem' className='icon-primary' />}
<MiniButton disabled={disabled || modified}
titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')} onClick={controller.moveUp}
icon={<IconMoveDown size='1.25rem' className='icon-primary' />} />
disabled={disabled || modified} <MiniButton
onClick={onMoveDown} titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')}
/> icon={<IconMoveDown size='1.25rem' className='icon-primary' />}
disabled={disabled || modified}
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),