feat(validator): auto fix missing property error

This commit is contained in:
Bowen Tan 2022-07-12 10:38:19 +08:00
parent f62c6ce384
commit dcff01f801
6 changed files with 74 additions and 9 deletions

View File

@ -15,6 +15,10 @@ import {
} from '@chakra-ui/react';
import { observer } from 'mobx-react-lite';
import React, { useMemo } from 'react';
import {
ModifyComponentPropertiesLeafOperation,
ModifyTraitPropertiesLeafOperation,
} from '../operations/leaf';
import { EditorServices } from '../types';
import { Pagination } from './Pagination';
@ -35,6 +39,25 @@ export const WarningArea: React.FC<Props> = observer(({ services }) => {
return validateResult
.slice(currPage * PageSize, currPage * PageSize + PageSize)
.map((result, i) => {
const onFix = (newProperties: any) => {
if (result.traitType && result.traitIndex) {
const operation = new ModifyTraitPropertiesLeafOperation(services.registry, {
componentId: result.componentId,
traitIndex: result.traitIndex,
properties: newProperties,
});
services.appModelManager.do(operation);
} else {
const operation = new ModifyComponentPropertiesLeafOperation(
services.registry,
{
componentId: result.componentId,
properties: newProperties,
}
);
services.appModelManager.do(operation);
}
};
return (
<Tr key={i}>
<Td
@ -47,10 +70,22 @@ export const WarningArea: React.FC<Props> = observer(({ services }) => {
<Td>{result.traitType || '-'}</Td>
<Td>{result.property || '-'}</Td>
<Td>{result.message}</Td>
<Td>
{result.fix ? (
<button onClick={() => onFix(result.fix?.())}>fix</button>
) : undefined}
</Td>
</Tr>
);
});
}, [currPage, isCollapsed, setSelectedComponentId, validateResult]);
}, [
currPage,
isCollapsed,
services.appModelManager,
services.registry,
setSelectedComponentId,
validateResult,
]);
const savedBadge = useMemo(() => {
return <Badge colorScheme="green">Saved</Badge>;
@ -114,6 +149,7 @@ export const WarningArea: React.FC<Props> = observer(({ services }) => {
<Th>Trait Type</Th>
<Th>Property</Th>
<Th>Message</Th>
<Th>Fix</Th>
</Tr>
</Thead>
<Tbody>{errorItems}</Tbody>

View File

@ -95,10 +95,11 @@ export class SchemaValidator implements ISchemaValidator {
});
this.traitRules.forEach(rule => {
component.traits.forEach(trait => {
component.traits.forEach((trait, i) => {
const r = rule.validate({
trait,
component,
traitIndex: i,
...baseContext,
});
if (r.length > 0) {
@ -128,6 +129,12 @@ export class SchemaValidator implements ISchemaValidator {
});
}
fix() {
this.result.forEach(r => {
r.fix?.();
});
}
private initAjv() {
this.ajv = new Ajv({})
.addKeyword('kind')

View File

@ -30,12 +30,14 @@ export interface ComponentValidateContext extends BaseValidateContext {
export interface TraitValidateContext extends BaseValidateContext {
trait: ITraitModel;
traitIndex: number;
component: IComponentModel;
}
export interface PropertiesValidateContext extends BaseValidateContext {
properties: IFieldModel;
trait?: ITraitModel;
traitIndex?: number;
component: IComponentModel;
}
@ -85,7 +87,8 @@ export interface ISchemaValidator {
export interface ValidateErrorResult {
componentId: string;
traitType?: string;
traitIndex?: number;
property?: string;
message: string;
fix?: () => void;
fix?: () => any;
}

View File

@ -1,5 +1,5 @@
import { get, has } from 'lodash';
import { ExpressionKeywords } from '@sunmao-ui/shared';
import { cloneDeep, get, has, set } from 'lodash';
import { ExpressionKeywords, generateDefaultValueFromSpec } from '@sunmao-ui/shared';
import { ComponentId, ModuleId } from '../../AppModel/IAppModel';
import {
PropertiesValidatorRule,
@ -15,6 +15,8 @@ class PropertySchemaValidatorRule implements PropertiesValidatorRule {
component,
trait,
validators,
traitIndex,
componentIdSpecMap,
}: PropertiesValidateContext): ValidateErrorResult[] {
const results: ValidateErrorResult[] = [];
let validate;
@ -47,6 +49,17 @@ class PropertySchemaValidatorRule implements PropertiesValidatorRule {
componentId: component.id,
property: error.instancePath,
traitType: trait?.type,
traitIndex,
fix: () => {
const defaultValue = generateDefaultValueFromSpec(
componentIdSpecMap[component.id].spec.properties
) as Object;
const path = instancePath.split('/').slice(1).join('.');
const newProperties = cloneDeep(properties.rawValue);
set(newProperties, path, get(defaultValue, path));
return newProperties;
},
});
}
});

View File

@ -3,8 +3,14 @@ import { generateDefaultValueFromSpec } from '../src/utils/spec';
describe('generateDefaultValueFromSpec function', () => {
it('can parse array', () => {
const type = Type.Array(Type.Object({}));
expect(generateDefaultValueFromSpec(type)).toMatchObject([]);
const type = Type.Array(
Type.Object({
foo: Type.Number(),
})
);
expect(generateDefaultValueFromSpec(type)).toEqual([{ foo: 0 }]);
const type2 = Type.Array(Type.String());
expect(generateDefaultValueFromSpec(type2)).toEqual(['']);
});
it('can parse number', () => {
const type = Type.Number();
@ -24,7 +30,7 @@ describe('generateDefaultValueFromSpec function', () => {
key: Type.String(),
value: Type.Array(Type.String()),
});
expect(generateDefaultValueFromSpec(type)).toMatchObject({ key: '', value: [] });
expect(generateDefaultValueFromSpec(type)).toMatchObject({ key: '', value: [''] });
});
it('can parse enum', () => {

View File

@ -85,7 +85,7 @@ export function generateDefaultValueFromSpec(spec: JSONSchema7): JSONSchema7Type
? Array.isArray(spec.items)
? getArray(spec.items)
: isJSONSchema(spec.items)
? []
? [generateDefaultValueFromSpec(spec.items)]
: null
: [];
case spec.type === 'number':