diff --git a/packages/client/src/components/basic/Message/Message.tsx b/packages/client/src/components/basic/Message/Message.tsx index 36fe03989..37975aff8 100644 --- a/packages/client/src/components/basic/Message/Message.tsx +++ b/packages/client/src/components/basic/Message/Message.tsx @@ -49,7 +49,9 @@ export const Message: React.FC = ({ warning, entities }) => { Missing{" "} - {position?.section ? positionObject[position?.section] : "actant"} + {position?.subSection + ? positionObject[position?.subSection] + : "actant"} :{" "} {"at least one actant of a matching type should be used"} @@ -59,7 +61,8 @@ export const Message: React.FC = ({ warning, entities }) => { return ( {`Actant's entity type does not match the Action`} - {position?.section && ` - ${positionObject[position?.section]}`} + {position?.subSection && + ` - ${positionObject[position?.subSection]}`} {entity && ` - [${entity.class}: ${getShortLabelByLetterCount( entity.label, @@ -71,7 +74,8 @@ export const Message: React.FC = ({ warning, entities }) => { return ( {`This actant position allows no actant`} - {position?.section && ` - ${positionObject[position?.section]}`} + {position?.subSection && + ` - ${positionObject[position?.subSection]}`} {entity && ` - [${entity.class}: ${getShortLabelByLetterCount( entity.label, @@ -83,14 +87,16 @@ export const Message: React.FC = ({ warning, entities }) => { return ( {`Entity type valencies of the actions not matching`} - {position?.section && ` - ${positionObject[position?.section]}`} + {position?.subSection && + ` - ${positionObject[position?.subSection]}`} ); case WarningTypeEnums.AVU: return ( {`Action valency not defined`} - {position?.section && ` - ${positionObject[position?.section]}`} + {position?.subSection && + ` - ${positionObject[position?.subSection]}`} {entity && ` - [${entity.class}: ${getShortLabelByLetterCount( entity.label, @@ -111,7 +117,13 @@ export const Message: React.FC = ({ warning, entities }) => { case WarningTypeEnums.MVAL: return Missing at least one entity-type valency; case WarningTypeEnums.AVAL: - return Asymmetrical valency; + return ( + + Asymmetrical valency + {position?.subSection && + ` - ${positionObject[position?.subSection]}`} + + ); case WarningTypeEnums.MAEE: return Missing action/event equivalent; default: diff --git a/packages/client/src/pages/MainPage/containers/EntityDetailBox/EntityDetail/EntityDetail.tsx b/packages/client/src/pages/MainPage/containers/EntityDetailBox/EntityDetail/EntityDetail.tsx index a518c4c65..77fd3f55e 100644 --- a/packages/client/src/pages/MainPage/containers/EntityDetailBox/EntityDetail/EntityDetail.tsx +++ b/packages/client/src/pages/MainPage/containers/EntityDetailBox/EntityDetail/EntityDetail.tsx @@ -39,6 +39,7 @@ import { StyledDetailSectionContentUsedIn, StyledDetailSectionEntityList, StyledDetailSectionHeader, + StyledDetailWarnings, StyledDetailWrapper, StyledPropGroupWrap, StyledUsedAsHeading, @@ -50,6 +51,7 @@ import { EntityDetailMetaPropsTable } from "./EntityDetailUsedInTable/EntityDeta import { EntityDetailStatementPropsTable } from "./EntityDetailUsedInTable/EntityDetailStatementPropsTable/EntityDetailStatementPropsTable"; import { EntityDetailStatementsTable } from "./EntityDetailUsedInTable/EntityDetailStatementsTable/EntityDetailStatementsTable"; import { EntityDetailValency } from "./EntityDetailValency/EntityDetailValency"; +import { IWarningPositionSection } from "@shared/types/warning"; const allowedEntityChangeClasses = [ EntityEnums.Class.Value, @@ -621,6 +623,18 @@ export const EntityDetail: React.FC = ({ detailId }) => { {entity.class === EntityEnums.Class.Action && ( Valency + + {entity.warnings && + entity.warnings + .filter( + (w) => + w.position?.section === + IWarningPositionSection.Valencies + ) + .map((warning, key) => { + return ; + })} + = ({ detailId }) => { {/* Relations */} Relations - {entity.warnings && - entity.warnings.map((warning, key) => { - return ; - })} + + {entity.warnings && + entity.warnings + .filter( + (w) => + w.position?.section === + IWarningPositionSection.Relations + ) + .map((warning, key) => { + return ; + })} + ` color: ${({ theme }) => theme.color["primary"]}; `; +export const StyledDetailWarnings = styled.div` + display: grid; + grid-gap: ${({ theme }) => theme.space["1"]}; + grid-auto-flow: row; +`; + export const StyledDetailContentRowValueID = styled.div` display: inline-flex; font-style: italic; diff --git a/packages/client/src/pages/MainPage/containers/StatementEditorBox/StatementEditor/StatementEditor.tsx b/packages/client/src/pages/MainPage/containers/StatementEditorBox/StatementEditor/StatementEditor.tsx index fe24306f4..7f01699b0 100644 --- a/packages/client/src/pages/MainPage/containers/StatementEditorBox/StatementEditor/StatementEditor.tsx +++ b/packages/client/src/pages/MainPage/containers/StatementEditorBox/StatementEditor/StatementEditor.tsx @@ -54,6 +54,7 @@ import { getEntityLabel, getShortLabelByLetterCount } from "utils"; import { EntityReferenceTable } from "../../EntityReferenceTable/EntityReferenceTable"; import { StyledBreadcrumbWrap, + StyledDetailWarnings, StyledEditorContentRow, StyledEditorContentRowLabel, StyledEditorContentRowValue, @@ -700,6 +701,7 @@ export const StatementEditor: React.FC = ({ {statement.warnings.length > 0 && ( + {statement.warnings.length} Warnings{" "} @@ -718,6 +720,7 @@ export const StatementEditor: React.FC = ({ /> + {showWarnings && statement.warnings .sort((a, b) => a.type.localeCompare(b.type)) @@ -730,6 +733,7 @@ export const StatementEditor: React.FC = ({ /> ); })} + )} diff --git a/packages/client/src/pages/MainPage/containers/StatementEditorBox/StatementEditorBoxStyles.tsx b/packages/client/src/pages/MainPage/containers/StatementEditorBox/StatementEditorBoxStyles.tsx index 427a3ba3c..952fe7ab0 100644 --- a/packages/client/src/pages/MainPage/containers/StatementEditorBox/StatementEditorBoxStyles.tsx +++ b/packages/client/src/pages/MainPage/containers/StatementEditorBox/StatementEditorBoxStyles.tsx @@ -61,6 +61,12 @@ export const StyledEditorSection = styled.div` } `; +export const StyledDetailWarnings = styled.div` + display: grid; + grid-gap: ${({ theme }) => theme.space["1"]}; + grid-auto-flow: row; +`; + interface StyledEditorSectionHeader {} export const StyledEditorSectionHeader = styled.div` display: flex; diff --git a/packages/server/src/models/entity/warnings.test.ts b/packages/server/src/models/entity/warnings.test.ts index 3232a350c..6baf9da31 100644 --- a/packages/server/src/models/entity/warnings.test.ts +++ b/packages/server/src/models/entity/warnings.test.ts @@ -268,4 +268,29 @@ describe("models/entity/warnings", function () { expect(maee).toBeFalsy(); }); }); + + describe("test hasAVAL", function () { + const db = new Db(); + const [, actionEntity] = prepareEntity(EntityEnums.Class.Action); + const [, aee] = prepareRelation(RelationEnums.Type.ActionEventEquivalent); + aee.entityIds = [actionEntity.id, "random"]; + + beforeAll(async () => { + await db.initDb(); + await aee.save(db.connection); + await actionEntity.save(db.connection); + }); + + afterAll(async () => { + await clean(db); + }); + + it("should test 1", async () => { + const sclm = await new EntityWarnings( + actionEntity.id, + actionEntity.class + ).hasAVAL(db.connection); + expect(sclm).toBeFalsy(); + }); + }); }); diff --git a/packages/server/src/models/entity/warnings.ts b/packages/server/src/models/entity/warnings.ts index c9ca77502..880b68c48 100644 --- a/packages/server/src/models/entity/warnings.ts +++ b/packages/server/src/models/entity/warnings.ts @@ -3,7 +3,9 @@ import Superclass from "@models/relation/superclass"; import { findEntityById } from "@service/shorthands"; import { EntityEnums, RelationEnums, WarningTypeEnums } from "@shared/enums"; import { IAction, IWarning } from "@shared/types"; +import { IActionValency } from "@shared/types/action"; import { InternalServerError } from "@shared/types/errors"; +import { IWarningPositionSection } from "@shared/types/warning"; import { Connection } from "rethinkdb-ts"; export default class EntityWarnings { @@ -21,10 +23,18 @@ export default class EntityWarnings { * @param relId * @returns new instance of warning */ - newWarning(warningType: WarningTypeEnums, relId?: string): IWarning { + newWarning( + warningType: WarningTypeEnums, + section: IWarningPositionSection, + pos?: keyof IActionValency + ): IWarning { return { type: warningType, - origin: relId || "", + position: { + section: section, + subSection: pos, + }, + origin: "", }; } @@ -51,6 +61,11 @@ export default class EntityWarnings { warnings.push(mvalWarning); } + const avalWarnings = await this.hasAVAL(conn); + if (avalWarnings) { + avalWarnings.forEach((w) => warnings.push(w)); + } + const maeeWarning = await this.hasMAEE(conn); if (maeeWarning) { warnings.push(maeeWarning); @@ -78,7 +93,7 @@ export default class EntityWarnings { ); const gotSCL = !!scls.find((s) => s.entityIds[0] === this.entityId); - return gotSCL ? null : this.newWarning(WarningTypeEnums.SCLM); + return gotSCL ? null : this.newWarning(WarningTypeEnums.SCLM, IWarningPositionSection.Relations); } /** @@ -135,7 +150,7 @@ export default class EntityWarnings { for (const baseClassIds of Object.values(baseIdsPerConcept)) { if (baseClassIds.indexOf(requiredBaseClassId) === -1) { // required base class is not present for this concept - return this.newWarning(WarningTypeEnums.ISYNC); + return this.newWarning(WarningTypeEnums.ISYNC, IWarningPositionSection.Relations); } } } @@ -143,11 +158,6 @@ export default class EntityWarnings { return null; } - /** - * Tests if there is MVAL warning and returns it - * @param conn - * @returns - */ async hasMVAL(conn: Connection): Promise { if (this.class !== EntityEnums.Class.Action) { return null; @@ -166,12 +176,88 @@ export default class EntityWarnings { action.data.entities.a2 === undefined && action.data.entities.s === undefined) ) { - return this.newWarning(WarningTypeEnums.MVAL); + return this.newWarning(WarningTypeEnums.MVAL, IWarningPositionSection.Valencies); } return null; } + /** + * Tests if there is AVAL warning and returns it + * @param conn + * @returns + */ + async hasAVAL(conn: Connection): Promise { + if (this.class !== EntityEnums.Class.Action) { + return null; + } + + const action = await findEntityById(conn, this.entityId); + if (!action) { + throw new InternalServerError( + "action not found while checking MVAL warning" + ); + } + + const relations = ( + await Relation.findForEntity(conn, this.entityId) + ).filter( + (r) => + [ + RelationEnums.Type.SubjectSemantics, + RelationEnums.Type.Actant1Semantics, + RelationEnums.Type.Actant2Semantics, + ].indexOf(r.type) !== -1 + ); + const warnings = [] + + for (const pos of Object.keys( + action.data.valencies + ) as (keyof IActionValency)[]) { + const types = action.data.entities[pos]; + const valency = action.data.valencies[pos]; + const morphosValid = valency && valency.length > 0; + const relIds = relations + .filter((r) => { + if (pos === "s") { + return r.type === RelationEnums.Type.SubjectSemantics; + } else if (pos === "a1") { + return r.type === RelationEnums.Type.Actant1Semantics; + } else { + return r.type === RelationEnums.Type.Actant2Semantics; + } + }) + .reduce((acc, curr) => { + acc = acc.concat(curr.entityIds); + return acc; + }, [] as string[]) + .filter((id) => id !== this.entityId); + + const semantFilled = relIds.length > 0; + const onlyEmptyAllowed = + !types || + !types.length || + (types.length === 1 && types[0] === EntityEnums.Extension.Empty); + if (!morphosValid && onlyEmptyAllowed && !relIds.length) { + continue; + } + + const entitiesSet = + relIds.length > 0 && + (types || []).length > 0 && + types?.find((t) => t !== EntityEnums.Extension.Empty); + + if (morphosValid && semantFilled && entitiesSet) { + continue; + } + + const newWarning = this.newWarning(WarningTypeEnums.AVAL, IWarningPositionSection.Valencies, pos); + warnings.push(newWarning) + } + + return warnings; + } + /** * Tests if there is MAEE warning and returns it * @param conn @@ -191,7 +277,7 @@ export default class EntityWarnings { ); if (!aee || !aee.length) { - return this.newWarning(WarningTypeEnums.MAEE); + return this.newWarning(WarningTypeEnums.MAEE, IWarningPositionSection.Relations); } return null; diff --git a/packages/server/src/models/statement/response.ts b/packages/server/src/models/statement/response.ts index dc972dc45..58c451bca 100644 --- a/packages/server/src/models/statement/response.ts +++ b/packages/server/src/models/statement/response.ts @@ -7,16 +7,16 @@ import { IStatement, } from "@shared/types"; import { OrderType } from "@shared/types/response-statement"; -import { IWarning, IWarningPosition } from "@shared/types/warning"; +import { IWarning, IWarningPosition, IWarningPositionSection } from "@shared/types/warning"; +import { ActionEntity } from "@models/action/action"; import { WarningTypeEnums } from "@shared/enums"; +import { InternalServerError } from "@shared/types/errors"; import { Connection } from "rethinkdb-ts"; import { IRequest } from "src/custom_typings/request"; -import Statement from "./statement"; import Entity from "../entity/entity"; -import { InternalServerError } from "@shared/types/errors"; -import { ActionEntity } from "@models/action/action"; import { PositionRules } from "./PositionRules"; +import Statement from "./statement"; export class ResponseStatement extends Statement implements IResponseStatement { entities: { [key: string]: IEntity }; @@ -128,7 +128,8 @@ export class ResponseStatement extends Statement implements IResponseStatement { if (entity && !allowedClasses.includes(entity.class)) { warnings.push( this.newStatementWarning(warningType, { - section: `${position}`, + section: IWarningPositionSection.Statement, + subSection: `${position}`, entityId: a.entityId, actantId: a.id, }) @@ -161,7 +162,8 @@ export class ResponseStatement extends Statement implements IResponseStatement { if (rules.mismatch) { warnings.push( this.newStatementWarning(WarningTypeEnums.WAC, { - section: `${position}`, + section: IWarningPositionSection.Statement, + subSection: `${position}`, }) ); } @@ -170,7 +172,8 @@ export class ResponseStatement extends Statement implements IResponseStatement { if (!rules.allowsEmpty() && !rules.allUndefined) { warnings.push( this.newStatementWarning(WarningTypeEnums.MA, { - section: `${position}`, + section: IWarningPositionSection.Statement, + subSection: `${position}`, }) ); } else if (rules.allUndefined) { @@ -181,7 +184,8 @@ export class ResponseStatement extends Statement implements IResponseStatement { rules.undefinedActions.forEach((actionId) => { warnings.push( this.newStatementWarning(WarningTypeEnums.AVU, { - section: position, + section: IWarningPositionSection.Statement, + subSection: position, entityId: actionId, }) ); @@ -205,7 +209,8 @@ export class ResponseStatement extends Statement implements IResponseStatement { } else if (PositionRules.allowsOnlyEmpty(actionRules)) { warnings.push( this.newStatementWarning(WarningTypeEnums.ANA, { - section: `${position}`, + section: IWarningPositionSection.Statement, + subSection: `${position}`, actantId: stActant.id, entityId: stActant.entityId, }) @@ -213,7 +218,8 @@ export class ResponseStatement extends Statement implements IResponseStatement { } else if (!actionRules.includes(actant.class)) { warnings.push( this.newStatementWarning(WarningTypeEnums.WA, { - section: `${position}`, + section: IWarningPositionSection.Statement, + subSection: `${position}`, actantId: stActant.id, entityId: stActant.entityId, }) diff --git a/packages/shared/types/warning.ts b/packages/shared/types/warning.ts index a48311325..c66220242 100644 --- a/packages/shared/types/warning.ts +++ b/packages/shared/types/warning.ts @@ -1,13 +1,22 @@ import { WarningTypeEnums } from "../enums"; +import { IActionValency } from "./action"; export interface IWarning { type: WarningTypeEnums; - position?: IWarningPosition; + position?: IWarningPosition origin: string; } export interface IWarningPosition { - section?: string; + + section?: IWarningPositionSection; + subSection?: string; entityId?: string; actantId?: string; } + +export enum IWarningPositionSection { + Relations = "Relations", + Valencies = "Valencies", + Statement = "Statement", +}