Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(restConfiguration): Add restConfiguration entity #970

Merged
merged 1 commit into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/ui/src/models/camel/camel-route-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AddStepMode } from '../visualization/base-visual-entity';
import { CamelRouteVisualEntity, isCamelFrom, isCamelRoute } from '../visualization/flows';
import { CamelErrorHandlerVisualEntity } from '../visualization/flows/camel-error-handler-visual-entity';
import { CamelOnExceptionVisualEntity } from '../visualization/flows/camel-on-exception-visual-entity';
import { CamelRestConfigurationVisualEntity } from '../visualization/flows/camel-rest-configuration-visual-entity';
import { NonVisualEntity } from '../visualization/flows/non-visual-entity';
import { CamelComponentFilterService } from '../visualization/flows/support/camel-component-filter.service';
import { CamelRouteVisualEntityData } from '../visualization/flows/support/camel-component-types';
Expand All @@ -15,7 +16,11 @@ import { BaseCamelEntity } from './entities';
import { SourceSchemaType } from './source-schema-type';

export class CamelRouteResource implements CamelResource, BeansAwareResource {
static readonly SUPPORTED_ENTITIES = [CamelOnExceptionVisualEntity, CamelErrorHandlerVisualEntity] as const;
static readonly SUPPORTED_ENTITIES = [
CamelOnExceptionVisualEntity,
CamelErrorHandlerVisualEntity,
CamelRestConfigurationVisualEntity,
] as const;
static readonly PARAMETERS_ORDER = ['id', 'description', 'uri', 'parameters', 'steps'];
readonly sortFn = createCamelPropertiesSorter(CamelRouteResource.PARAMETERS_ORDER) as (
a: unknown,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import * as catalogIndex from '@kaoto-next/camel-catalog/index.json';
import { RestConfiguration } from '@kaoto-next/camel-catalog/types';
import { restConfigurationSchema, restConfigurationStub } from '../../../stubs/rest-configuration';
import { ICamelProcessorDefinition } from '../../camel-processors-catalog';
import { EntityType } from '../../camel/entities';
import { CatalogKind } from '../../catalog-kind';
import { CamelCatalogService } from './camel-catalog.service';
import { CamelRestConfigurationVisualEntity } from './camel-rest-configuration-visual-entity';

describe('CamelRestConfigurationVisualEntity', () => {
const REST_CONFIGURATION_ID_REGEXP = /^restConfiguration-[a-zA-Z0-9]{4}$/;
let restConfigurationDef: { restConfiguration: RestConfiguration };

beforeAll(async () => {
const entityCatalogMap = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.entities.file);
CamelCatalogService.setCatalogKey(
CatalogKind.Entity,
entityCatalogMap as unknown as Record<string, ICamelProcessorDefinition>,
);
});

afterAll(() => {
CamelCatalogService.clearCatalogs();
});

beforeEach(() => {
restConfigurationDef = {
restConfiguration: {
...restConfigurationStub.restConfiguration,
},
};
});

describe('isApplicable', () => {
it.each([
[true, { restConfiguration: {} }],
[true, { restConfiguration: { bindingMode: 'off' } }],
[true, restConfigurationStub],
[false, { from: { id: 'from-1234', steps: [] } }],
[false, { restConfiguration: { bindingMode: 'off' }, anotherProperty: true }],
])('should return %s for %s', (result, definition) => {
expect(CamelRestConfigurationVisualEntity.isApplicable(definition)).toEqual(result);
});
});

describe('constructor', () => {
it('should set id to generated id', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.id).toMatch(REST_CONFIGURATION_ID_REGEXP);
});
});

it('should return id', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getId()).toMatch(REST_CONFIGURATION_ID_REGEXP);
});

it('should set id', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);
const newId = 'newId';
entity.setId(newId);

expect(entity.getId()).toEqual(newId);
});

it('should return node label', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getNodeLabel()).toEqual('restConfiguration');
});

it('should return tooltip content', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getTooltipContent()).toEqual('restConfiguration');
});

describe('getComponentSchema', () => {
it('should return entity current definition', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getComponentSchema().definition).toEqual(restConfigurationDef.restConfiguration);
});

it.skip('should return schema from store', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getComponentSchema().schema).toEqual(restConfigurationSchema);
});
Comment on lines +87 to +91
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to reenable this test, once #969 is fixed.


it('should return hardcoded schema title', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getComponentSchema().title).toEqual('Rest Configuration');
});
});

describe('updateModel', () => {
it('should update model', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);
const path = 'restConfiguration.bindingMode';
const value = 'json';

entity.updateModel(path, value);

expect(restConfigurationDef.restConfiguration.bindingMode).toEqual(value);
});

it('should not update model if path is not defined', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);
const value = 'json_xml';

entity.updateModel(undefined, value);

expect(restConfigurationDef.restConfiguration.bindingMode).toEqual('off');
});

it('should reset the restConfiguration object if it is not defined', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

entity.updateModel('restConfiguration', {});

expect(restConfigurationDef.restConfiguration).toEqual({});
});
});

it('return no interactions', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getNodeInteraction()).toEqual({
canHavePreviousStep: false,
canHaveNextStep: false,
canHaveChildren: false,
canHaveSpecialChildren: false,
canRemoveStep: false,
canReplaceStep: false,
});
});

/** Skip until https://github.com/KaotoIO/kaoto-next/issues/969 gets resolved */
describe.skip('getNodeValidationText', () => {
it('should return undefined for valid definitions', () => {
const entity = new CamelRestConfigurationVisualEntity({
restConfiguration: {
...restConfigurationDef.restConfiguration,
useXForwardHeaders: true,
apiVendorExtension: true,
skipBindingOnErrorCode: true,
clientRequestValidation: true,
enableCORS: true,
enableNoContentResponse: true,
inlineRoutes: true,
},
});

expect(entity.getNodeValidationText()).toBeUndefined();
});

it('should not modify the original definition when validating', () => {
const originalRestConfigurationDef: RestConfiguration = { ...restConfigurationDef.restConfiguration };
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

entity.getNodeValidationText();

expect(restConfigurationDef.restConfiguration).toEqual(originalRestConfigurationDef);
});

it('should return errors when there is an invalid property', () => {
const invalidRestConfigurationDef: RestConfiguration = {
...restConfigurationDef.restConfiguration,
bindingMode: 'WildModeOn' as RestConfiguration['bindingMode'],
};
const entity = new CamelRestConfigurationVisualEntity({ restConfiguration: invalidRestConfigurationDef });

expect(entity.getNodeValidationText()).toEqual(`'/useXForwardHeaders' must be boolean,
'/apiVendorExtension' must be boolean,
'/skipBindingOnErrorCode' must be boolean,
'/clientRequestValidation' must be boolean,
'/enableCORS' must be boolean,
'/enableNoContentResponse' must be boolean,
'/inlineRoutes' must be boolean`);
});
});
Comment on lines +142 to +185
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to reenable this test, once #969 is fixed.


describe('toVizNode', () => {
it('should return visualization node', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

const vizNode = entity.toVizNode();

expect(vizNode.data).toEqual({
componentName: undefined,
entity: {
id: entity.getId(),
restConfigurationDef,
type: EntityType.RestConfiguration,
},
icon: '',
isGroup: true,
path: 'restConfiguration',
processorName: 'restConfiguration',
});
});
});

it('should serialize the restConfiguration definition', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.toJSON()).toEqual(restConfigurationDef);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { ProcessorDefinition, RestConfiguration } from '@kaoto-next/camel-catalog/types';
import Ajv, { ValidateFunction } from 'ajv-draft-04';
import addFormats from 'ajv-formats';
import { getCamelRandomId } from '../../../camel-utils/camel-random-id';
import { isDefined, setValue } from '../../../utils';
import { EntityType } from '../../camel/entities/base-entity';
import { CatalogKind } from '../../catalog-kind';
import {
BaseVisualCamelEntity,
IVisualizationNode,
IVisualizationNodeData,
NodeInteraction,
VisualComponentSchema,
} from '../base-visual-entity';
import { CamelCatalogService } from './camel-catalog.service';
import { CamelStepsService } from './support/camel-steps.service';

export class CamelRestConfigurationVisualEntity implements BaseVisualCamelEntity {
id: string;
readonly type = EntityType.RestConfiguration;
private schemaValidator: ValidateFunction<RestConfiguration> | undefined;

constructor(public restConfigurationDef: { restConfiguration: RestConfiguration }) {
const id = getCamelRandomId('restConfiguration');
this.id = id;
}

static isApplicable(restConfigurationDef: unknown): restConfigurationDef is { restConfiguration: RestConfiguration } {
if (
!isDefined(restConfigurationDef) ||
Array.isArray(restConfigurationDef) ||
typeof restConfigurationDef !== 'object'
) {
return false;
}

const objectKeys = Object.keys(restConfigurationDef!);

return (
objectKeys.length === 1 &&
'restConfiguration' in restConfigurationDef! &&
typeof restConfigurationDef.restConfiguration === 'object'
);
}

getId(): string {
return this.id;
}

setId(id: string): void {
this.id = id;
}

getNodeLabel(): string {
return 'restConfiguration';
}

getTooltipContent(): string {
return 'restConfiguration';
}

addStep(): void {
return;
}

removeStep(): void {
return;
}

getComponentSchema(): VisualComponentSchema {
const schema = CamelCatalogService.getComponent(CatalogKind.Entity, 'restConfiguration');

return {
definition: Object.assign({}, this.restConfigurationDef.restConfiguration),
schema: schema?.propertiesSchema || {},
title: 'Rest Configuration',
};
}

updateModel(path: string | undefined, value: unknown): void {
if (!path) return;

setValue(this.restConfigurationDef, path, value);

if (!isDefined(this.restConfigurationDef.restConfiguration)) {
this.restConfigurationDef.restConfiguration = {};
}
}

getNodeInteraction(): NodeInteraction {
return {
canHavePreviousStep: false,
canHaveNextStep: false,
canHaveChildren: false,
canHaveSpecialChildren: false,
canRemoveStep: false,
canReplaceStep: false,
};
}

getNodeValidationText(): string | undefined {
const componentVisualSchema = this.getComponentSchema();
if (!componentVisualSchema) return undefined;

if (!this.schemaValidator) {
this.schemaValidator = this.getValidatorFunction(componentVisualSchema);
}

this.schemaValidator?.({ ...this.restConfigurationDef.restConfiguration });

return this.schemaValidator?.errors?.map((error) => `'${error.instancePath}' ${error.message}`).join(',\n');
}

toVizNode(): IVisualizationNode<IVisualizationNodeData> {
const restConfigurationGroupNode = CamelStepsService.getVizNodeFromProcessor(
'restConfiguration',
{ processorName: 'restConfiguration' as keyof ProcessorDefinition },
this.restConfigurationDef,
);
restConfigurationGroupNode.data.entity = this;
restConfigurationGroupNode.data.isGroup = true;

return restConfigurationGroupNode;
}

toJSON(): unknown {
return this.restConfigurationDef;
}

private getValidatorFunction(
componentVisualSchema: VisualComponentSchema,
): ValidateFunction<RestConfiguration> | undefined {
const ajv = new Ajv({
strict: false,
allErrors: true,
useDefaults: 'empty',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@igarashitm , I think you already saw this, but in any case, this useDefaults: 'empty' causes us to write all default values in the model, and since we don't want that, I'm validating a copy of the model, so the original don't get updated.

});
addFormats(ajv);

let schemaValidator: ValidateFunction<RestConfiguration> | undefined;
try {
schemaValidator = ajv.compile<RestConfiguration>(componentVisualSchema.schema);
} catch (error) {
console.error('Could not compile schema', error);
}

return schemaValidator;
}
}
Loading
Loading