R: Add inherited cst source to API

This commit is contained in:
Ivan 2024-09-14 15:15:36 +03:00
parent 713d436aa6
commit 80680a7bf2
14 changed files with 122 additions and 45 deletions

View File

@ -58,7 +58,6 @@ class TestChangeAttributes(EndpointTester):
self.operation3.refresh_from_db() self.operation3.refresh_from_db()
self.ks3 = RSForm(self.operation3.result) self.ks3 = RSForm(self.operation3.result)
@decl_endpoint('/api/library/{item}/set-owner', method='patch') @decl_endpoint('/api/library/{item}/set-owner', method='patch')
def test_set_owner(self): def test_set_owner(self):
data = {'user': self.user3.pk} data = {'user': self.user3.pk}

View File

@ -57,6 +57,33 @@ class TestChangeConstituents(EndpointTester):
self.ks3 = RSForm(self.operation3.result) self.ks3 = RSForm(self.operation3.result)
self.assertEqual(self.ks3.constituents().count(), 4) self.assertEqual(self.ks3.constituents().count(), 4)
@decl_endpoint('/api/rsforms/{item}/details', method='get')
def test_retrieve_inheritance(self):
response = self.executeOK(item=self.ks3.model.pk)
self.assertEqual(response.data['oss'], [{'id': self.owned.model.pk, 'alias': 'T1'}])
self.assertEqual(response.data['inheritance'], [
{
'child': Constituenta.objects.get(as_child__parent_id=self.ks1X1.pk).pk,
'child_source': self.ks3.model.pk,
'parent': self.ks1X1.pk, 'parent_source': self.ks1.model.pk
},
{
'child': Constituenta.objects.get(as_child__parent_id=self.ks1X2.pk).pk,
'child_source': self.ks3.model.pk,
'parent': self.ks1X2.pk, 'parent_source': self.ks1.model.pk
},
{
'child': Constituenta.objects.get(as_child__parent_id=self.ks2X1.pk).pk,
'child_source': self.ks3.model.pk,
'parent': self.ks2X1.pk, 'parent_source': self.ks2.model.pk
},
{
'child': Constituenta.objects.get(as_child__parent_id=self.ks2D1.pk).pk,
'child_source': self.ks3.model.pk,
'parent': self.ks2D1.pk, 'parent_source': self.ks2.model.pk
},
])
@decl_endpoint('/api/rsforms/{schema}/create-cst', method='post') @decl_endpoint('/api/rsforms/{schema}/create-cst', method='post')
def test_create_constituenta(self): def test_create_constituenta(self):
data = { data = {

View File

@ -4,6 +4,7 @@ from .basics import (
ASTNodeSerializer, ASTNodeSerializer,
ExpressionParseSerializer, ExpressionParseSerializer,
ExpressionSerializer, ExpressionSerializer,
InheritanceDataSerializer,
MultiFormSerializer, MultiFormSerializer,
ResolverSerializer, ResolverSerializer,
TextSerializer, TextSerializer,

View File

@ -122,6 +122,14 @@ class ReferenceSerializer(serializers.Serializer):
pos_output = TextPositionSerializer() pos_output = TextPositionSerializer()
class InheritanceDataSerializer(serializers.Serializer):
''' Serializer: inheritance data. '''
child = serializers.IntegerField()
child_source = serializers.IntegerField()
parent = serializers.IntegerField()
parent_source = serializers.IntegerField()
class ResolverSerializer(serializers.Serializer): class ResolverSerializer(serializers.Serializer):
''' Serializer: Resolver results serializer. ''' ''' Serializer: Resolver results serializer. '''
input = serializers.CharField() input = serializers.CharField()

View File

@ -17,7 +17,7 @@ from apps.oss.models import Inheritance
from shared import messages as msg from shared import messages as msg
from ..models import Constituenta, CstType, RSForm from ..models import Constituenta, CstType, RSForm
from .basics import CstParseSerializer from .basics import CstParseSerializer, InheritanceDataSerializer
from .io_pyconcept import PyConceptAdapter from .io_pyconcept import PyConceptAdapter
@ -103,7 +103,7 @@ class RSFormSerializer(serializers.ModelSerializer):
child=CstSerializer() child=CstSerializer()
) )
inheritance = serializers.ListField( inheritance = serializers.ListField(
child=serializers.ListField(child=serializers.IntegerField()) child=InheritanceDataSerializer()
) )
oss = serializers.ListField( oss = serializers.ListField(
child=LibraryItemReferenceSerializer() child=LibraryItemReferenceSerializer()
@ -116,8 +116,17 @@ class RSFormSerializer(serializers.ModelSerializer):
def to_representation(self, instance: LibraryItem) -> dict: def to_representation(self, instance: LibraryItem) -> dict:
result = self.to_base_data(instance) result = self.to_base_data(instance)
for link in Inheritance.objects.filter(Q(child__schema=instance) | Q(parent__schema=instance)): inheritances = Inheritance.objects \
result['inheritance'].append([link.child.pk, link.parent.pk]) .filter(Q(child__schema=instance) | Q(parent__schema=instance)) \
.select_related('parent__schema', 'child__schema') \
.only('parent__id', 'parent__schema__id', 'child__id', 'child__schema__id')
for link in inheritances:
result['inheritance'].append({
'child': link.child_id,
'child_source': link.child.schema_id,
'parent': link.parent_id,
'parent_source': link.parent.schema_id
})
return result return result
def to_base_data(self, instance: LibraryItem) -> dict: def to_base_data(self, instance: LibraryItem) -> dict:

View File

@ -39,16 +39,16 @@ function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaPro
{data.definition_resolved} {data.definition_resolved}
</p> </p>
) : null} ) : null}
{data.parent_alias ? ( {data.spawner_alias ? (
<p> <p>
<b>Основание: </b> <b>Основание: </b>
{data.parent_alias} {data.spawner_alias}
</p> </p>
) : null} ) : null}
{data.children_alias.length > 0 ? ( {data.spawn_alias.length > 0 ? (
<p> <p>
<b>Порождает: </b> <b>Порождает: </b>
{data.children_alias.join(', ')} {data.spawn_alias.join(', ')}
</p> </p>
) : null} ) : null}
{data.convention ? ( {data.convention ? (

View File

@ -3,6 +3,7 @@
*/ */
import { Graph } from './Graph'; import { Graph } from './Graph';
import { LibraryItemID } from './library';
import { ConstituentaID, CstType, IConstituenta, IRSForm, IRSFormData, IRSFormStats } from './rsform'; import { ConstituentaID, CstType, IConstituenta, IRSForm, IRSFormData, IRSFormStats } from './rsform';
import { inferClass, inferStatus, inferTemplate, isBaseSet, isFunctional } from './rsformAPI'; import { inferClass, inferStatus, inferTemplate, isBaseSet, isFunctional } from './rsformAPI';
import { ParsingStatus, ValueClass } from './rslang'; import { ParsingStatus, ValueClass } from './rslang';
@ -59,27 +60,34 @@ export class RSFormLoader {
} }
private inferCstAttributes() { private inferCstAttributes() {
const inherit_children = new Set(this.schema.inheritance.map(item => item[0])); const parent_schemas = new Map<ConstituentaID, LibraryItemID>();
const inherit_parents = new Set(this.schema.inheritance.map(item => item[1])); this.schema.inheritance.forEach(item => {
if (item.child_source === this.schema.id) {
parent_schemas.set(item.child, item.parent_source);
}
});
const inherit_children = new Set(this.schema.inheritance.map(item => item.child));
const inherit_parents = new Set(this.schema.inheritance.map(item => item.parent));
this.graph.topologicalOrder().forEach(cstID => { this.graph.topologicalOrder().forEach(cstID => {
const cst = this.cstByID.get(cstID)!; const cst = this.cstByID.get(cstID)!;
cst.status = inferStatus(cst.parse.status, cst.parse.valueClass); cst.status = inferStatus(cst.parse.status, cst.parse.valueClass);
cst.is_template = inferTemplate(cst.definition_formal); cst.is_template = inferTemplate(cst.definition_formal);
cst.cst_class = inferClass(cst.cst_type, cst.is_template); cst.cst_class = inferClass(cst.cst_type, cst.is_template);
cst.children = []; cst.spawn = [];
cst.children_alias = []; cst.spawn_alias = [];
cst.parent_schema = parent_schemas.get(cst.id);
cst.is_inherited = inherit_children.has(cst.id); cst.is_inherited = inherit_children.has(cst.id);
cst.is_inherited_parent = inherit_parents.has(cst.id); cst.has_inherited_children = inherit_parents.has(cst.id);
cst.is_simple_expression = this.inferSimpleExpression(cst); cst.is_simple_expression = this.inferSimpleExpression(cst);
if (!cst.is_simple_expression || cst.cst_type === CstType.STRUCTURED) { if (!cst.is_simple_expression || cst.cst_type === CstType.STRUCTURED) {
return; return;
} }
cst.parent = this.inferParent(cst); cst.spawner = this.inferParent(cst);
if (cst.parent) { if (cst.spawner) {
const parent = this.cstByID.get(cst.parent)!; const parent = this.cstByID.get(cst.spawner)!;
cst.parent_alias = parent.alias; cst.spawner_alias = parent.alias;
parent.children.push(cst.id); parent.spawn.push(cst.id);
parent.children_alias.push(cst.alias); parent.spawn_alias.push(cst.alias);
} }
}); });
} }
@ -107,7 +115,7 @@ export class RSFormLoader {
if (sources.size !== 1 || sources.has(target.id)) { if (sources.size !== 1 || sources.has(target.id)) {
return undefined; return undefined;
} }
const parent_id = sources.values().next().value as ConstituentaID; const parent_id = sources.values().next().value!;
const parent = this.cstByID.get(parent_id); const parent = this.cstByID.get(parent_id);
if (parent && isBaseSet(parent.cst_type)) { if (parent && isBaseSet(parent.cst_type)) {
return undefined; return undefined;
@ -122,7 +130,7 @@ export class RSFormLoader {
node.inputs.forEach(id => { node.inputs.forEach(id => {
const parent = this.cstByID.get(id)!; const parent = this.cstByID.get(id)!;
if (!parent.is_template || !parent.is_simple_expression) { if (!parent.is_template || !parent.is_simple_expression) {
sources.add(parent.parent ?? id); sources.add(parent.spawner ?? id);
} }
}); });
return sources; return sources;
@ -133,7 +141,7 @@ export class RSFormLoader {
bodyDependencies.forEach(alias => { bodyDependencies.forEach(alias => {
const parent = this.cstByAlias.get(alias); const parent = this.cstByAlias.get(alias);
if (parent && (!parent.is_template || !parent.is_simple_expression)) { if (parent && (!parent.is_template || !parent.is_simple_expression)) {
sources.add(this.cstByID.get(parent.id)!.parent ?? parent.id); sources.add(this.cstByID.get(parent.id)!.spawner ?? parent.id);
} }
}); });
const needCheckHead = () => { const needCheckHead = () => {
@ -142,7 +150,7 @@ export class RSFormLoader {
} else if (sources.size !== 1) { } else if (sources.size !== 1) {
return false; return false;
} else { } else {
const base = this.cstByID.get(sources.values().next().value as ConstituentaID)!; const base = this.cstByID.get(sources.values().next().value!)!;
return !isFunctional(base.cst_type) || splitTemplateDefinition(base.definition_formal).head !== expression.head; return !isFunctional(base.cst_type) || splitTemplateDefinition(base.definition_formal).head !== expression.head;
} }
}; };
@ -151,7 +159,7 @@ export class RSFormLoader {
headDependencies.forEach(alias => { headDependencies.forEach(alias => {
const parent = this.cstByAlias.get(alias); const parent = this.cstByAlias.get(alias);
if (parent && !isBaseSet(parent.cst_type) && (!parent.is_template || !parent.is_simple_expression)) { if (parent && !isBaseSet(parent.cst_type) && (!parent.is_template || !parent.is_simple_expression)) {
sources.add(parent.parent ?? parent.id); sources.add(parent.spawner ?? parent.id);
} }
}); });
} }

View File

@ -44,6 +44,6 @@ export function applyNodeSizing(target: IConstituenta, sizing: GraphSizing): num
} else if (sizing === 'complex') { } else if (sizing === 'complex') {
return target.is_simple_expression ? 1 : 2; return target.is_simple_expression ? 1 : 2;
} else { } else {
return target.parent ? 1 : 2; return target.spawner ? 1 : 2;
} }
} }

View File

@ -101,16 +101,31 @@ export interface IConstituentaData extends IConstituentaMeta {
* Represents Constituenta. * Represents Constituenta.
*/ */
export interface IConstituenta extends IConstituentaData { export interface IConstituenta extends IConstituentaData {
/** {@link CstClass} of this {@link IConstituenta}. */
cst_class: CstClass; cst_class: CstClass;
/** {@link ExpressionStatus} of this {@link IConstituenta}. */
status: ExpressionStatus; status: ExpressionStatus;
/** Indicates if this {@link IConstituenta} is a template. */
is_template: boolean; is_template: boolean;
/** Indicates if this {@link IConstituenta} has a simple expression. */
is_simple_expression: boolean; is_simple_expression: boolean;
/** {@link LibraryItemID} that contains parent of this inherited {@link IConstituenta}. */
parent_schema?: LibraryItemID;
/** Indicates if this {@link IConstituenta} is inherited. */
is_inherited: boolean; is_inherited: boolean;
is_inherited_parent: boolean; /** Indicates if this {@link IConstituenta} has children that are inherited. */
parent?: ConstituentaID; has_inherited_children: boolean;
parent_alias?: string;
children: number[]; /** {@link IConstituenta} that spawned this one. */
children_alias: string[]; spawner?: ConstituentaID;
/** Alias of {@link IConstituenta} that spawned this one. */
spawner_alias?: string;
/** List of {@link IConstituenta} that are spawned by this one. */
spawn: number[];
/** List of aliases of {@link IConstituenta} that are spawned by this one. */
spawn_alias: string[];
} }
/** /**
@ -198,12 +213,22 @@ export interface IRSFormStats {
count_theorem: number; count_theorem: number;
} }
/**
* Represents inheritance data for {@link IRSForm}.
*/
export interface IInheritanceData {
child: ConstituentaID;
child_source: LibraryItemID;
parent: ConstituentaID;
parent_source: LibraryItemID;
}
/** /**
* Represents data for {@link IRSForm} provided by backend. * Represents data for {@link IRSForm} provided by backend.
*/ */
export interface IRSFormData extends ILibraryItemVersioned { export interface IRSFormData extends ILibraryItemVersioned {
items: IConstituentaData[]; items: IConstituentaData[];
inheritance: ConstituentaID[][]; inheritance: IInheritanceData[];
oss: ILibraryItemReference[]; oss: ILibraryItemReference[];
} }

View File

@ -117,9 +117,9 @@ export function inferClass(type: CstType, isTemplate: boolean = false): CstClass
export function createMockConstituenta(id: ConstituentaID, alias: string, comment: string): IConstituenta { export function createMockConstituenta(id: ConstituentaID, alias: string, comment: string): IConstituenta {
return { return {
id: id, id: id,
parent: id, spawner: id,
children: [], spawn: [],
children_alias: [], spawn_alias: [],
is_simple_expression: false, is_simple_expression: false,
schema: -1, schema: -1,
alias: alias, alias: alias,
@ -134,7 +134,7 @@ export function createMockConstituenta(id: ConstituentaID, alias: string, commen
status: ExpressionStatus.INCORRECT, status: ExpressionStatus.INCORRECT,
is_template: false, is_template: false,
is_inherited: false, is_inherited: false,
is_inherited_parent: false, has_inherited_children: false,
cst_class: CstClass.DERIVED, cst_class: CstClass.DERIVED,
parse: { parse: {
status: ParsingStatus.INCORRECT, status: ParsingStatus.INCORRECT,

View File

@ -249,7 +249,7 @@ function FormConstituenta({
icon={<IconSave size='1.25rem' />} icon={<IconSave size='1.25rem' />}
/> />
<Overlay position='top-[0.1rem] left-[0.4rem]' className='cc-icons'> <Overlay position='top-[0.1rem] left-[0.4rem]' className='cc-icons'>
{state.is_inherited_parent && !state.is_inherited ? ( {state.has_inherited_children && !state.is_inherited ? (
<Indicator <Indicator
icon={<IconPredecessor size='1.25rem' className='clr-text-primary' />} icon={<IconPredecessor size='1.25rem' className='clr-text-primary' />}
titleHtml='Внимание!</br> Конституента имеет потомков<br/> в операционной схеме синтеза' titleHtml='Внимание!</br> Конституента имеет потомков<br/> в операционной схеме синтеза'

View File

@ -45,7 +45,7 @@ function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams,
} }
if (!focusCst && params.foldDerived) { if (!focusCst && params.foldDerived) {
schema.items.forEach(cst => { schema.items.forEach(cst => {
if (cst.parent) { if (cst.spawner) {
graph.foldNode(cst.id); graph.foldNode(cst.id);
} }
}); });
@ -53,7 +53,7 @@ function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams,
if (focusCst) { if (focusCst) {
const includes: ConstituentaID[] = [ const includes: ConstituentaID[] = [
focusCst.id, focusCst.id,
...focusCst.children, ...focusCst.spawn,
...(params.focusShowInputs ? schema.graph.expandInputs([focusCst.id]) : []), ...(params.focusShowInputs ? schema.graph.expandInputs([focusCst.id]) : []),
...(params.focusShowOutputs ? schema.graph.expandOutputs([focusCst.id]) : []) ...(params.focusShowOutputs ? schema.graph.expandOutputs([focusCst.id]) : [])
]; ];

View File

@ -109,13 +109,13 @@ function TableSideConstituents({
} }
}, },
{ {
when: (cst: IConstituenta) => !!activeCst && cst.parent === activeCst?.id && cst.id !== activeCst?.id, when: (cst: IConstituenta) => !!activeCst && cst.spawner === activeCst?.id && cst.id !== activeCst?.id,
style: { style: {
backgroundColor: colors.bgOrange50 backgroundColor: colors.bgOrange50
} }
}, },
{ {
when: (cst: IConstituenta) => activeCst?.id !== undefined && cst.children.includes(activeCst.id), when: (cst: IConstituenta) => activeCst?.id !== undefined && cst.spawn.includes(activeCst.id),
style: { style: {
backgroundColor: colors.bgGreen50 backgroundColor: colors.bgGreen50
} }

View File

@ -215,15 +215,15 @@ export function domTooltipConstituenta(cst?: IConstituenta, canClick?: boolean):
dom.appendChild(convention); dom.appendChild(convention);
} }
if (cst.parent_alias) { if (cst.spawner_alias) {
const derived = document.createElement('p'); const derived = document.createElement('p');
derived.innerHTML = `<b>Основание:</b> ${cst.parent_alias}`; derived.innerHTML = `<b>Основание:</b> ${cst.spawner_alias}`;
dom.appendChild(derived); dom.appendChild(derived);
} }
if (cst.children_alias.length > 0) { if (cst.spawn_alias.length > 0) {
const children = document.createElement('p'); const children = document.createElement('p');
children.innerHTML = `<b>Порождает:</b> ${cst.children_alias.join(', ')}`; children.innerHTML = `<b>Порождает:</b> ${cst.spawn_alias.join(', ')}`;
dom.appendChild(children); dom.appendChild(children);
} }