Skip to content

Commit

Permalink
feat: Import Definition (#3399)
Browse files Browse the repository at this point in the history
  • Loading branch information
xoscar committed Nov 24, 2023
1 parent c5c43de commit 9b9e06c
Show file tree
Hide file tree
Showing 22 changed files with 295 additions and 137 deletions.
216 changes: 91 additions & 125 deletions web/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions web/package.json
Expand Up @@ -37,6 +37,7 @@
"fast-xml-parser": "4.2.4",
"history": "5.2.0",
"html-react-parser": "3.0.4",
"js-yaml": "4.1.0",
"less": "4.1.3",
"lodash": "4.17.21",
"markdown-it": "13.0.1",
Expand Down
5 changes: 2 additions & 3 deletions web/src/components/ImportModal/ImportModal.tsx
Expand Up @@ -4,7 +4,6 @@ import {TDraftTest} from 'types/Test.types';
import {ImportTypes} from 'constants/Test.constants';
import ImportService from 'services/Import.service';
import {useDashboard} from 'providers/Dashboard/Dashboard.provider';
import {ImportTypeToPlugin} from 'constants/Plugins.constants';
import {useCreateTest} from 'providers/CreateTest/CreateTest.provider';
import {ImportSelector} from 'components/Inputs';
import ImportFactory from 'components/TestPlugins/ImportFactory';
Expand Down Expand Up @@ -36,7 +35,7 @@ const ImportModal = ({isOpen, onClose}: IProps) => {
const handleImport = useCallback(
async (values: TDraftTest) => {
const draft = await ImportService.getRequest(type, values);
const plugin = ImportTypeToPlugin[type];
const plugin = await ImportService.getPlugin(type, values);

onInitialValues(draft);
navigate(`/test/create/${plugin.type}`);
Expand Down Expand Up @@ -66,7 +65,7 @@ const ImportModal = ({isOpen, onClose}: IProps) => {
layout="vertical"
name={FORM_ID}
onFinish={handleImport}
initialValues={{importType: ImportTypes.curl}}
initialValues={{importType: ImportTypes.definition}}
onValuesChange={(_: any, values) => handleChange(values)}
>
<S.Container>
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/ImportModal/Tip.tsx
Expand Up @@ -6,7 +6,7 @@ const Tip = () => (
<Typography.Title level={3} type="secondary">
<BulbOutlined /> What are the supported formats?
</Typography.Title>
<Typography.Text type="secondary">We support cURL & Postman. OpenAPI is coming soon!</Typography.Text>
<Typography.Text type="secondary">We support Tracetest Definition, cURL & Postman. OpenAPI is coming soon!</Typography.Text>
</>
);

Expand Down
Expand Up @@ -13,6 +13,7 @@ const CurlCommand = ({value, onChange}: IEditorProps) => {
extensions={[StreamLanguage.define(shell)]}
spellCheck={false}
placeholder="curl -X POST http://site.com"
autoFocus
/>
);
};
Expand Down
32 changes: 32 additions & 0 deletions web/src/components/Inputs/Editor/Definition/Definition.tsx
@@ -0,0 +1,32 @@
import CodeMirror from '@uiw/react-codemirror';
import {StreamLanguage} from '@codemirror/language';
import {yaml} from '@codemirror/legacy-modes/mode/yaml';
import {IEditorProps} from '../Editor';

const placeholder = `type: Test
spec:
name: My Test
trigger:
type: http
httpRequest:
method: GET
url: google.com
`;

const Definition = ({value, onChange}: IEditorProps) => {
return (
<CodeMirror
value={value}
onChange={onChange}
data-cy="definition-editor"
basicSetup={{indentOnInput: true}}
extensions={[StreamLanguage.define(yaml)]}
spellCheck={false}
placeholder={placeholder}
minHeight="200px"
autoFocus
/>
);
};

export default Definition;
2 changes: 2 additions & 0 deletions web/src/components/Inputs/Editor/Definition/index.ts
@@ -0,0 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export {default} from './Definition';
1 change: 1 addition & 0 deletions web/src/components/Inputs/Editor/Editor.tsx
Expand Up @@ -11,6 +11,7 @@ const EditorMap = {
[SupportedEditors.Selector]: lazy(() => import('./Selector')),
[SupportedEditors.Interpolation]: lazy(() => import('./Interpolation')),
[SupportedEditors.CurlCommand]: lazy(() => import('./CurlCommand')),
[SupportedEditors.Definition]: lazy(() => import('./Definition')),
} as const;

export interface IEditorProps {
Expand Down
Expand Up @@ -3,7 +3,7 @@ import {ImportTypes} from 'constants/Test.constants';
import ImportCard from './ImportCard';
import * as S from './ImportSelector.styled';

const importList = [ImportTypes.curl, ImportTypes.postman];
const importList = [ImportTypes.definition, ImportTypes.curl, ImportTypes.postman];

interface IProps {
value?: ImportTypes;
Expand Down
2 changes: 2 additions & 0 deletions web/src/components/TestPlugins/ImportFactory.tsx
Expand Up @@ -3,10 +3,12 @@ import {TDraftTestForm} from 'types/Test.types';
import Postman from './Imports/Postman';
import Curl from './Imports/Curl';
import useShortcut from './hooks/useShortcut';
import Definition from './Imports/Definition';

const ImportFactoryMap = {
[ImportTypes.postman]: Postman,
[ImportTypes.curl]: Curl,
[ImportTypes.definition]: Definition,
};

export interface IFormProps {
Expand Down
@@ -0,0 +1,31 @@
import {DeleteOutlined} from '@ant-design/icons';
import {Typography} from 'antd';
import styled from 'styled-components';

export const Row = styled.div`
display: flex;
`;

export const Label = styled(Typography.Text).attrs({as: 'div'})`
margin-bottom: 8px;
`;

export const URLInputContainer = styled.div`
display: flex;
align-items: flex-start;
.ant-form-item {
margin: 0;
}
`;

export const HeaderContainer = styled.div`
align-items: center;
display: flex;
margin-bottom: 8px;
`;

export const DeleteIcon = styled(DeleteOutlined)`
color: ${({theme}) => theme.color.textSecondary};
font-size: ${({theme}) => theme.size.md};
`;
28 changes: 28 additions & 0 deletions web/src/components/TestPlugins/Imports/Definition/Definition.tsx
@@ -0,0 +1,28 @@
import {Form} from 'antd';
import {Editor} from 'components/Inputs';
import {SupportedEditors} from 'constants/Editor.constants';
import DefinitionService from 'services/Importers/Definition.service';

const Definition = () => {
return (
<Form.Item
label="Paste Tracetest Definition"
name="definition"
rules={[
{required: true, message: 'Please enter a valid Tracetest Definition'},
{
validator: (_, definition) => {
const errors = DefinitionService.validate(definition);
if (errors.length) return Promise.reject(new Error(errors.join(', ')));

return Promise.resolve();
},
},
]}
>
<Editor type={SupportedEditors.Definition} />
</Form.Item>
);
};

export default Definition;
2 changes: 2 additions & 0 deletions web/src/components/TestPlugins/Imports/Definition/index.ts
@@ -0,0 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export {default} from './Definition';
1 change: 1 addition & 0 deletions web/src/constants/Editor.constants.ts
Expand Up @@ -70,6 +70,7 @@ export enum SupportedEditors {
Interpolation = 'interpolation-editor',
Expression = 'expression-editor',
CurlCommand = 'curlCommand-editor',
Definition = 'definition-editor',
}

export const operatorList = [
Expand Down
7 changes: 1 addition & 6 deletions web/src/constants/Plugins.constants.ts
@@ -1,6 +1,6 @@
import {IPlugin} from 'types/Plugins.types';
import {SupportedPlugins} from './Common.constants';
import {ImportTypes, TriggerTypes} from './Test.constants';
import {TriggerTypes} from './Test.constants';

export enum ComponentNames {
SelectPlugin = 'SelectPlugin',
Expand Down Expand Up @@ -61,8 +61,3 @@ export const TriggerTypeToPlugin = {
[TriggerTypes.kafka]: Plugins.Kafka,
[TriggerTypes.traceid]: Plugins.TraceID,
} as const;

export const ImportTypeToPlugin = {
[ImportTypes.curl]: Plugins.REST,
[ImportTypes.postman]: Plugins.REST,
} as const;
1 change: 1 addition & 0 deletions web/src/constants/Test.constants.ts
Expand Up @@ -15,6 +15,7 @@ export enum TriggerTypes {
export enum ImportTypes {
postman = 'postman',
curl = 'curl',
definition = 'definition',
}

export enum SortBy {
Expand Down
6 changes: 6 additions & 0 deletions web/src/models/Test.model.ts
@@ -1,3 +1,4 @@
import {load} from 'js-yaml';
import {Model, TTestSchemas} from 'types/Common.types';
import TestOutput from './TestOutput.model';
import TestSpecs from './TestSpecs.model';
Expand Down Expand Up @@ -48,4 +49,9 @@ Test.FromRawTest = ({
};
};

Test.FromDefinition = (definition: string): Test => {
const raw: TRawTestResource = load(definition);
return Test(raw);
};

export default Test;
7 changes: 7 additions & 0 deletions web/src/services/Import.service.ts
@@ -1,11 +1,14 @@
import {ImportTypes} from 'constants/Test.constants';
import {TDraftTest} from 'types/Test.types';
import {IPlugin} from 'types/Plugins.types';
import CurlService from './Importers/Curl.service';
import PostmanService from './Importers/Postman.service';
import DefinitionService from './Importers/Definition.service';

const ImportServiceMap = {
[ImportTypes.curl]: CurlService,
[ImportTypes.postman]: PostmanService,
[ImportTypes.definition]: DefinitionService,
} as const;

const ImportService = () => ({
Expand All @@ -16,6 +19,10 @@ const ImportService = () => ({
async validateDraft(type: ImportTypes, draft: TDraftTest): Promise<boolean> {
return ImportServiceMap[type].validateDraft(draft);
},

async getPlugin(type: ImportTypes, draft: TDraftTest): Promise<IPlugin> {
return ImportServiceMap[type].getPlugin(draft);
},
});

export default ImportService();
5 changes: 5 additions & 0 deletions web/src/services/Importers/Curl.service.ts
@@ -1,6 +1,7 @@
import parseCurl from 'parse-curl';
import {ICurlValues, IImportService} from 'types/Test.types';
import Validator from 'utils/Validator';
import {Plugins} from 'constants/Plugins.constants';
import {HTTP_METHOD} from 'constants/Common.constants';

interface ICurlTriggerService extends IImportService {
Expand All @@ -27,6 +28,10 @@ const CurlTriggerService = (): ICurlTriggerService => ({
return Validator.required(url) && Validator.required(method);
},

getPlugin() {
return Plugins.REST;
},

getRequestFromCommand(command) {
const {url = '', method, header = {}, body} = parseCurl(command);

Expand Down
65 changes: 65 additions & 0 deletions web/src/services/Importers/Definition.service.ts
@@ -0,0 +1,65 @@
import {load} from 'js-yaml';
import {IDefinitionValues, IImportService} from 'types/Test.types';
import {TriggerTypeToPlugin} from 'constants/Plugins.constants';
import Test, {TRawTestResource} from 'models/Test.model';
import TestService from '../Test.service';
import {isJson} from '../../utils/Common';

interface IDefinitionImportService extends IImportService {
validate(definition: string): string[];
load(raw: string): TRawTestResource;
}

const DefinitionImportService = (): IDefinitionImportService => ({
async getRequest(values) {
const {definition} = values as IDefinitionValues;

const test = Test.FromDefinition(definition);
return TestService.getInitialValues(test);
},

async validateDraft(draft) {
const {definition} = draft as IDefinitionValues;

return !this.validate(definition).length;
},

getPlugin(draft) {
const {definition} = draft as IDefinitionValues;
const {spec = {}}: TRawTestResource = this.load(definition);

const triggerType = spec.trigger?.type;
return TriggerTypeToPlugin[triggerType!];
},

load(raw) {
if (isJson(raw)) return JSON.parse(raw) as TRawTestResource;

return load(raw) as TRawTestResource;
},

validate(raw) {
const {type, spec = {}}: TRawTestResource = this.load(raw);
const errors = [];

if (type !== 'Test') {
errors.push(`Invalid type: ${type}`);
}

if (!spec.name) {
errors.push(`Missing Name`);
}

if (!spec.trigger) {
errors.push(`Missing trigger`);
}

if (!spec.trigger?.type) {
errors.push('Missing trigger type');
}

return errors;
},
});

export default DefinitionImportService();
6 changes: 6 additions & 0 deletions web/src/services/Importers/Postman.service.tsx
@@ -1,6 +1,7 @@
import {Collection, Item, ItemGroup, Request, RequestAuthDefinition, VariableDefinition} from 'postman-collection';
import {HTTP_METHOD} from 'constants/Common.constants';
import {IImportService, IPostmanValues, TDraftTestForm, TRequestAuth} from 'types/Test.types';
import {Plugins} from 'constants/Plugins.constants';
import Validator from 'utils/Validator';
import HttpService from '../Triggers/Http.service';

Expand Down Expand Up @@ -45,6 +46,11 @@ const Postman = (): IPostmanTriggerService => ({
const draft = await this.valuesFromRequest(requests, variables, collectionTest || '');
return !!draft && HttpService.validateDraft(draft);
},

getPlugin() {
return Plugins.REST;
},

valuesFromRequest(requests, variables, identifier) {
const request = requests.find(({id}) => identifier === id);
if (request) {
Expand Down
9 changes: 8 additions & 1 deletion web/src/types/Test.types.ts
Expand Up @@ -7,6 +7,7 @@ import HttpRequest from 'models/HttpRequest.model';
import TraceIDRequest from 'models/TraceIDRequest.model';
import KafkaRequest from 'models/KafkaRequest.model';
import {Model, TGrpcSchemas, THttpSchemas, TKafkaSchemas} from './Common.types';
import {IPlugin} from './Plugins.types';

export type TRequestAuth = THttpSchemas['HTTPRequest']['auth'];
export type TMethod = THttpSchemas['HTTPRequest']['method'];
Expand Down Expand Up @@ -62,6 +63,10 @@ export interface ICurlValues extends IHttpValues {
command: string;
}

export interface IDefinitionValues {
definition: string;
}

export interface IBasicValues {
name: string;
description: string;
Expand All @@ -74,6 +79,7 @@ export interface ITraceIDValues extends IHttpValues {
}

export type TTestRequestDetailsValues = IRpcValues | IHttpValues | IPostmanValues | ICurlValues | ITraceIDValues;

export type TDraftTest<T = TTestRequestDetailsValues> = Partial<IBasicValues & T>;
export type TDraftTestForm<T = TTestRequestDetailsValues> = FormInstance<TDraftTest<T>>;

Expand All @@ -85,6 +91,7 @@ export interface ITriggerService {
}

export interface IImportService {
getRequest(values: TDraftTest): Promise<TDraftTest>;
getRequest(draft: TDraftTest): Promise<TDraftTest>;
validateDraft(draft: TDraftTest): Promise<boolean>;
getPlugin(draft: TDraftTest): IPlugin;
}

0 comments on commit 9b9e06c

Please sign in to comment.