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: Added fault-injection plugin form #1740

Closed
wants to merge 15 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable no-undef */

context('Create and Delete Consumer', () => {
beforeEach(() => {
cy.login();

cy.fixture('selector.json').as('domSelector');
cy.fixture('data.json').as('data');
});

const selector = {
abort_http_status: "#abort_http_status",
delay_duration: "#delay_duration",
}

const data = {
time: 200,
}

it('creates consumer with fault-injection form', function () {
cy.visit('/');
cy.contains('Consumer').click();
cy.get(this.domSelector.empty).should('be.visible');
cy.contains('Create').click();
// basic information
cy.get(this.domSelector.username).type(this.data.consumerName);
cy.get(this.domSelector.description).type(this.data.description);
cy.contains('Next').click();

// config auth plugin
cy.contains(this.domSelector.pluginCard, 'key-auth').within(() => {
cy.contains('Enable').click({ force: true });
});
cy.focused(this.domSelector.drawer).should('exist');
cy.get(this.domSelector.disabledSwitcher).click().should('have.class', 'ant-switch-checked');
// edit codemirror
cy.get(this.domSelector.codeMirror)
.first()
.then((editor) => {
editor[0].CodeMirror.setValue(
JSON.stringify({
key: 'test',
}),
);
cy.contains('button', 'Submit').click();
});

cy.contains(this.domSelector.pluginCard, 'fault-injection').within(() => {
cy.contains('Enable').click({
force: true,
});
});

cy.focused(this.domSelector.drawer).should('exist');
// config proxy-mirror form
cy.get(this.domSelector.drawer).within(() => {
cy.contains('Submit').click({
force: true,
});
});
cy.get(this.domSelector.notification).should('contain', 'Invalid plugin data');
cy.get(this.domSelector.notificationCloseIcon).click().should('not.exist');

cy.get(selector.abort_http_status).type(data.time);
cy.get(selector.delay_duration).type(data.time);
cy.get(this.domSelector.drawer).within(() => {
cy.contains('Submit').click({
force: true,
});
});
cy.get(this.domSelector.drawer).should('not.exist');

cy.contains('button', 'Next').click();
cy.contains('button', 'Submit').click();
cy.get(this.domSelector.notification).should('contain', this.data.createConsumerSuccess);
cy.get(this.domSelector.notificationCloseIcon).click().should('not.exist');
});

it('delete the consumer', function () {
cy.visit('/consumer/list');
cy.contains(this.data.consumerName).should('be.visible').siblings().contains('Delete').click();
cy.contains('button', 'Confirm').click();
cy.get(this.domSelector.notification).should('contain', this.data.deleteConsumerSuccess);
});
});
201 changes: 201 additions & 0 deletions web/src/components/Plugin/UI/fault-injection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import type { FormInstance } from 'antd/es/form';
import { Button, Form, Input, InputNumber, Select } from 'antd';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { useIntl } from 'umi';

type Props = {
form: FormInstance;
};

const FORM_ITEM_LAYOUT = {
labelCol: {
span: 6,
},
wrapperCol: {
span: 7,
},
};

const FORM_LIST_LAYOUT = {
labelCol: {
span: 6,
},
wrapperCol: {
span: 10,
},
};

const FORM_ITEM_WITHOUT_LABEL = {
wrapperCol: {
sm: { span: 10, offset: 6 },
},
};

const FaultInjection: React.FC<Props> = ({ form }) => {
const { formatMessage } = useIntl()

return (
<Form
form={form}
{...FORM_ITEM_LAYOUT}
>
<Form.Item
name={['abort', 'http_status']}
label='abort.http_status'
rules={[{ required: true, message: 'Please input abort.http_status!' }]}
Copy link
Member

Choose a reason for hiding this comment

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

add i18n please

tooltip={formatMessage({ id: "component.pluginForm.fault-injection.abort.http_status.tooltip" })}
>
<InputNumber min={200} required />
</Form.Item>
<Form.Item
name={['abort', 'percentage']}
label='abort.percentage'
tooltip={formatMessage({ id: "component.pluginForm.fault-injection.abort.percentage.tooltip" })}
>
<InputNumber min={0} max={100} />
</Form.Item>
<Form.Item
name={['abort', 'body']}
label='abort.body'
tooltip={formatMessage({ id: "component.pluginForm.fault-injection.abort.body.tooltip" })}
>
<Input min={200} />
</Form.Item>

<Form.List name={['abort', 'vars']}>
Copy link
Member

Choose a reason for hiding this comment

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

We need to convert the var to the following: "vars": [ [ [ "arg_name","==","jack" ] ] ]

{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item
{...(index === 0 ? FORM_LIST_LAYOUT : FORM_ITEM_WITHOUT_LABEL)}
label={index === 0 && 'abort.vars'}
tooltip={formatMessage({ id: "component.pluginForm.fault-injection.abort.vars.tooltip" })}
key={field.key}
>
<Form.Item
{...field}
validateTrigger={['onChange', 'onBlur']}
noStyle
>
<Select style={{ width: '70%' }}>
{['==', '`~=', '>', '<', '~~', '~*', 'in', 'has', '!'].map((item) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be better to define a global constant, it would be reuse both here and route vars

return <Select.Option value={item} key={item}>{item}</Select.Option>
})}
</Select>
</Form.Item>
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
style={{ margin: '0 8px' }}
onClick={() => {
remove(field.name);
}}
/>
) : null}
</Form.Item>
))}
{
<Form.Item {...FORM_ITEM_WITHOUT_LABEL}>
<Button
type="dashed"
onClick={() => {
add();
}}
>
<PlusOutlined /> {`${formatMessage({ id: 'component.global.create' })} about.vars`}
</Button>
</Form.Item>
}
</div>
);
}}
</Form.List>

<Form.Item
label="delay.duration"
name={['delay', 'duration']}
rules={[{ required: true, message: 'Please input delay.duration!' }]}
tooltip={formatMessage({ id: "component.pluginForm.fault-injection.delay.duration.tooltip" })}
>
<InputNumber required />
</Form.Item>
<Form.Item
label="delay.percentage"
name={['delay', 'percentage']}
tooltip={formatMessage({ id: "component.pluginForm.fault-injection.delay.percentage.tooltip" })}
>
<InputNumber min={0} max={100} />
</Form.Item>

<Form.List name={['delay', 'vars']}>
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item
{...(index === 0 ? FORM_LIST_LAYOUT : FORM_ITEM_WITHOUT_LABEL)}
key={field.key}
label={index === 0 && 'delay.vars'}
tooltip={formatMessage({ id: "component.pluginForm.fault-injection.delay.vars.tooltip" })}
>
<Form.Item
{...field}
validateTrigger={['onChange', 'onBlur']}
noStyle
>
<Select style={{ width: '70%' }}>
{['==', '`~=', '>', '<', '~~', '~*', 'in', 'has', '!'].map((item) => {
return <Select.Option value={item} key={item}>{item}</Select.Option>
})}
</Select>
</Form.Item>
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
style={{ margin: '0 8px' }}
onClick={() => {
remove(field.name);
}}
/>
) : null}
</Form.Item>
))}
{
<Form.Item {...FORM_ITEM_WITHOUT_LABEL}>
<Button
type="dashed"
onClick={() => {
add();
}}
>
<PlusOutlined /> {`${formatMessage({ id: 'component.global.create' })} about.vars`}
</Button>
</Form.Item>
}
</div>
);
}}
</Form.List>
</Form>
);
}

export default FaultInjection;
5 changes: 4 additions & 1 deletion web/src/components/Plugin/UI/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ import { Empty } from 'antd';
import { useIntl } from 'umi';

import BasicAuth from './basic-auth'
import FaultInjection from './fault-injection';

type Props = {
name: string,
form: FormInstance,
renderForm: boolean
}

export const PLUGIN_UI_LIST = ['basic-auth',];
export const PLUGIN_UI_LIST = ['basic-auth', 'fault-injection'];

export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {

Expand All @@ -38,6 +39,8 @@ export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
switch (name) {
case 'basic-auth':
return <BasicAuth form={form} />
case 'fault-injection':
return <FaultInjection form={form} />
default:
return null;
}
Expand Down
9 changes: 9 additions & 0 deletions web/src/components/Plugin/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,13 @@ export default {
'component.step.select.pluginTemplate.select.option': 'Custom',
'component.plugin.pluginTemplate.tip1': '1. When a route already have plugins field configured, the plugins in the plugin template will be merged into it.',
'component.plugin.pluginTemplate.tip2': '2. The same plugin in the plugin template will override one in the plugins',

// limit-conn
'component.pluginForm.fault-injection.abort.http_status.tooltip': 'User-specified http code returned to the client.',
'component.pluginForm.fault-injection.abort.body.tooltip': 'Response data returned to the client. Nginx variable can be used inside, like client addr: $remote_addr.',
'component.pluginForm.fault-injection.abort.percentage.tooltip': 'Percentage of requests to be aborted.',
'component.pluginForm.fault-injection.abort.vars.tooltip': 'The rules for executing fault injection will only be executed when the rules are matched. vars is a list of expressions, which is from the lua-resty-expr.',
'component.pluginForm.fault-injection.delay.duration.tooltip': 'Delay time (can be decimal).',
'component.pluginForm.fault-injection.delay.percentage.tooltip': 'Percentage of requests to be delayed.',
'component.pluginForm.fault-injection.delay.vars.tooltip': 'Execute the request delay rule, and the request will be delayed only after the rule matches. vars is a list of expressions, which is from the lua-resty-expr.',
};
9 changes: 9 additions & 0 deletions web/src/components/Plugin/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,13 @@ export default {
'component.step.select.pluginTemplate.select.option': '手动配置',
'component.plugin.pluginTemplate.tip1': '1. 若路由已配置插件,则插件模板数据将与已配置的插件数据合并。',
'component.plugin.pluginTemplate.tip2': '2. 插件模板相同的插件会覆盖掉原有的插件。',

// limit-conn
'component.pluginForm.fault-injection.abort.http_status.tooltip': '返回给客户端的 http 状态码。',
'component.pluginForm.fault-injection.abort.body.tooltip': '返回给客户端的响应数据。支持使用 Nginx 变量,如 client addr: $remote_addr。',
'component.pluginForm.fault-injection.abort.percentage.tooltip': '将被中断的请求占比。',
'component.pluginForm.fault-injection.abort.vars.tooltip': '执行故障注入的规则,当规则匹配通过后才会执行故障注。vars 是一个表达式的列表,来自 lua-resty-expr。',
'component.pluginForm.fault-injection.delay.duration.tooltip': '延迟时间,可以指定小数。',
'component.pluginForm.fault-injection.delay.percentage.tooltip': '将被延迟的请求占比。',
'component.pluginForm.fault-injection.delay.vars.tooltip': '执行请求延迟的规则,当规则匹配通过后才会延迟请求。vars 是一个表达式列表,来自 lua-resty-expr。',
};