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: Import Definition #3399

Merged
merged 1 commit into from
Nov 24, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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;
}