Skip to content

Commit

Permalink
feat: Indication that some of the connectors mandatory properties are…
Browse files Browse the repository at this point in the history
… not yet configured

Fixes: #126
  • Loading branch information
igarashitm committed Jan 18, 2024
1 parent 1d2998c commit f03624e
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DataFormatEditor } from './DataFormatEditor';
import { LoadBalancerEditor } from './LoadBalancerEditor';
import { StepExpressionEditor } from './StepExpressionEditor';
import { CanvasNode } from './canvas.models';
import { ModelValidationService } from '../../../models/visualization/flows/support/model-validation.service';

interface CanvasFormProps {
selectedNode: CanvasNode;
Expand Down Expand Up @@ -53,10 +54,13 @@ export const CanvasForm: FunctionComponent<CanvasFormProps> = (props) => {

const handleOnChange = useCallback(
(newModel: Record<string, unknown>) => {
props.selectedNode.data?.vizNode?.updateModel(newModel);
if (props.selectedNode.data?.vizNode) {
props.selectedNode.data.vizNode.updateModel(newModel);
ModelValidationService.validateNodeStatus(visualComponentSchema, newModel, props.selectedNode.data.vizNode);
}
entitiesContext?.updateSourceCodeFromEntities();
},
[entitiesContext, props.selectedNode.data?.vizNode, props.selectedNode.id],
[entitiesContext, props.selectedNode.data?.vizNode, visualComponentSchema],
);

const isExpressionAwareStep = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ export class CanvasService {
width: CanvasDefaults.DEFAULT_NODE_DIAMETER,
height: CanvasDefaults.DEFAULT_NODE_DIAMETER,
shape: CanvasDefaults.DEFAULT_NODE_SHAPE,
status: options.data?.vizNode?.getStatus(),
};
}

Expand Down
26 changes: 23 additions & 3 deletions packages/ui/src/components/Visualization/Custom/CustomNode.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { DefaultNode, Node, WithSelectionProps, withContextMenu, withSelection } from '@patternfly/react-topology';
import { FunctionComponent } from 'react';
import {
DefaultNode,
Node,
NodeStatus,
withContextMenu,
withSelection,
WithSelectionProps,
} from '@patternfly/react-topology';
import { FunctionComponent, useEffect, useMemo, useState } from 'react';
import { AddStepMode } from '../../../models/visualization/base-visual-entity';
import { CanvasDefaults } from '../Canvas/canvas.defaults';
import { CanvasNode } from '../Canvas/canvas.models';
Expand All @@ -16,9 +23,22 @@ interface CustomNodeProps extends WithSelectionProps {
const CustomNode: FunctionComponent<CustomNodeProps> = ({ element, ...rest }) => {
const vizNode = element.getData()?.vizNode;
const label = vizNode?.getNodeLabel();
const statusDecoratorTooltip = useMemo(() => vizNode?.statusDecoratorTooltip, [vizNode?.statusDecoratorTooltip]);

useEffect(() => {
element.setNodeStatus(vizNode?.status || NodeStatus.default);
}, [element, vizNode?.status]);

return (
<DefaultNode element={element} label={label} truncateLength={15} {...rest} showStatusDecorator>
<DefaultNode
element={element}
label={label}
truncateLength={15}
{...rest}
showStatusDecorator
statusDecoratorTooltip={statusDecoratorTooltip}
onStatusDecoratorClick={() => {}}
>
<g data-testid={`custom-node__${vizNode?.id}`} data-nodelabel={label}>
<foreignObject
x="0"
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/src/hooks/entities.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ describe('useEntities', () => {
id: route-1234
from:
id: from-1234
uri: timer:template
uri: timer
parameters:
period: "1000"
timerName: template
steps:
- log:
id: log-1234
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ exports[`createCamelResource should create an empty KameletResource if no args i
"id": "from-1234",
"parameters": {
"period": "{{period}}",
"timerName": "user",
},
"steps": [
{
Expand All @@ -17,7 +18,7 @@ exports[`createCamelResource should create an empty KameletResource if no args i
"to": "kamelet:sink",
},
],
"uri": "timer:user",
"uri": "timer",
},
"id": "kamelet-1234",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ exports[`KameletResource should convert to JSON 1`] = `
"id": "from-1234",
"parameters": {
"period": "{{period}}",
"timerName": "user",
},
"steps": [
{
Expand All @@ -50,7 +51,7 @@ exports[`KameletResource should convert to JSON 1`] = `
"to": "kamelet:sink",
},
],
"uri": "timer:user",
"uri": "timer",
},
},
"types": {
Expand Down Expand Up @@ -103,6 +104,7 @@ exports[`KameletResource should create a new KameletResource 1`] = `
"id": "from-1234",
"parameters": {
"period": "{{period}}",
"timerName": "user",
},
"steps": [
{
Expand All @@ -112,7 +114,7 @@ exports[`KameletResource should create a new KameletResource 1`] = `
"to": "kamelet:sink",
},
],
"uri": "timer:user",
"uri": "timer",
},
},
"types": {
Expand Down Expand Up @@ -167,6 +169,7 @@ exports[`KameletResource should get the visual entities (Camel Route Visual Enti
"id": "from-1234",
"parameters": {
"period": "{{period}}",
"timerName": "user",
},
"steps": [
{
Expand All @@ -176,7 +179,7 @@ exports[`KameletResource should get the visual entities (Camel Route Visual Enti
"to": "kamelet:sink",
},
],
"uri": "timer:user",
"uri": "timer",
},
"id": "kamelet-1234",
},
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/src/models/visualization/base-visual-entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { JSONSchemaType } from 'ajv';
import { DefinedComponent } from '../camel-catalog-index';
import { BaseCamelEntity, EntityType } from '../camel/entities';
import { NodeStatus } from '@patternfly/react-topology';

/**
* BaseVisualCamelEntity
Expand Down Expand Up @@ -57,6 +58,8 @@ export interface BaseVisualCamelEntity extends BaseCamelEntity {
export interface IVisualizationNode<T extends IVisualizationNodeData = IVisualizationNodeData> {
id: string;
data: T;
status: NodeStatus | undefined;
statusDecoratorTooltip: string | undefined;

/** This property is only set on the root node */
getBaseEntity(): BaseVisualCamelEntity | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
ICamelElementLookupResult,
} from './support/camel-component-types';
import { EntityType } from '../../camel/entities';
import { ModelValidationService } from './support/model-validation.service';

export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity {
constructor(public route: RouteDefinition) {}
Expand Down Expand Up @@ -240,6 +241,9 @@ export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity
childrenVizNodes.forEach((childVizNode) => vizNode.addChild(childVizNode));
});

const schema = this.getComponentSchema(path);
const model = get(this.route, path);
ModelValidationService.validateNodeStatus(schema, model, vizNode);
return vizNode;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { IVisualizationNode, VisualComponentSchema } from '../../base-visual-entity';
import { NodeStatus } from '@patternfly/react-topology';
import { JSONSchemaType } from 'ajv';

export interface IValidationResult {
level: 'error' | 'warning' | 'info';
type: 'missingRequired';
parentPath: string;
propertyName: string;
message: string;
}

export class ModelValidationService {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static validateModel(schema: JSONSchemaType<unknown>, model: any, parentPath: string): IValidationResult[] {
const answer = [] as IValidationResult[];
if (schema.properties) {
Object.entries(schema.properties).forEach(([propertyName, propertyValue]) => {
const propertySchema = propertyValue as JSONSchemaType<unknown>;
// TODO
if (propertySchema.type === 'array') return;
if (propertySchema.type === 'object') {
const path = parentPath ? `${parentPath}.${propertyName}` : propertyName;
answer.push(...ModelValidationService.validateModel(propertySchema, model[propertyName], path));
return;
}
// check missing required parameter
if (
schema.required?.includes(propertyName) &&
propertySchema.default === undefined &&
(!model || !model[propertyName])
) {
answer.push({
level: 'error',
type: 'missingRequired',
parentPath: parentPath,
propertyName: propertyName,
message: `Missing required property ${propertyName}`,
});
}
});
}
return answer;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
static validateNodeStatus(schema: VisualComponentSchema | undefined, model: any, vizNode: IVisualizationNode): void {
if (!schema?.schema) return;

const validationResult = ModelValidationService.validateModel(schema.schema, model, '');
const missingProperties = validationResult
.filter((result) => result.type === 'missingRequired')
.map((result) => result.propertyName);
if (missingProperties.length > 0) {
const message =
missingProperties.length > 1
? `${missingProperties.length} required properties are not yet configured: [ ${missingProperties} ]`
: `1 required property is not yet configured: [ ${missingProperties} ]`;
vizNode.status = NodeStatus.warning;
vizNode.statusDecoratorTooltip = message;
} else {
vizNode.status = NodeStatus.success;
vizNode.statusDecoratorTooltip = undefined;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ spec:
template:
from:
id: ${getCamelRandomId('from')}
uri: "timer:user"
uri: "timer"
parameters:
timerName: user
period: "{{period}}"
steps:
- to: https://random-data-api.com/api/v2/users
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ export const routeTemplate = () => {
id: ${getCamelRandomId('route')}
from:
id: ${getCamelRandomId('from')}
uri: timer:template
uri: timer
parameters:
timerName: template
period: "1000"
steps:
- log:
Expand Down
19 changes: 19 additions & 0 deletions packages/ui/src/models/visualization/visualization-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
NodeInteraction,
VisualComponentSchema,
} from './base-visual-entity';
import { NodeStatus } from '@patternfly/react-topology';

export const createVisualizationNode = <T extends IVisualizationNodeData = IVisualizationNodeData>(
id: string,
Expand All @@ -24,6 +25,8 @@ class VisualizationNode<T extends IVisualizationNodeData = IVisualizationNodeDat
private previousNode: IVisualizationNode | undefined = undefined;
private nextNode: IVisualizationNode | undefined = undefined;
private children: IVisualizationNode[] | undefined;
public status: NodeStatus | undefined = undefined;
public statusDecoratorTooltip: string | undefined = undefined;

constructor(
public readonly id: string,
Expand Down Expand Up @@ -128,4 +131,20 @@ class VisualizationNode<T extends IVisualizationNodeData = IVisualizationNodeDat
/** If this node has children, populate the leaf nodes ids of each child */
this.children?.forEach((child) => child.populateLeafNodesIds(ids));
}

getStatus(): NodeStatus | undefined {
return this.status;
}

setStatus(status: NodeStatus) {
this.status = status;
}

getStatusDecoratorTooltip(): string | undefined {
return this.statusDecoratorTooltip;
}

setStatusDecoratorTooltip(tooltip: string) {
this.statusDecoratorTooltip = tooltip;
}
}

0 comments on commit f03624e

Please sign in to comment.