Skip to content

Commit

Permalink
feat: add rawDataEditor edit mode (#1601)
Browse files Browse the repository at this point in the history
  • Loading branch information
LiteSun authored Mar 22, 2021
1 parent fe407d1 commit 548bbe3
Show file tree
Hide file tree
Showing 14 changed files with 430 additions and 44 deletions.
86 changes: 86 additions & 0 deletions web/cypress/fixtures/rawDataEditor-dataset.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"Route": {
"uris": [
"/*"
],
"name": "root",
"methods": [
"GET",
"HEAD",
"POST",
"PUT",
"DELETE",
"OPTIONS",
"PATCH"
],
"upstream": {
"nodes": [
{
"host": "12.12.12.12",
"port": 80,
"weight": 1
}
],
"timeout": {
"connect": 6000,
"read": 6000,
"send": 6000
},
"type": "roundrobin",
"pass_host": "pass"
},
"status": 1
},
"Upstream": {
"nodes": [
{
"host": "12.12.12.12",
"port": 80,
"weight": 1
}
],
"timeout": {
"connect": 6,
"read": 6,
"send": 6
},
"type": "roundrobin",
"pass_host": "pass",
"name": "test"
},
"Service": {
"name": "test",
"upstream": {
"nodes": [
{
"host": "12.12.12.12",
"port": 80,
"weight": 1
}
],
"timeout": {
"connect": 6,
"read": 6,
"send": 6
},
"type": "roundrobin",
"pass_host": "pass"
},
"plugins": {
"jwt-auth": {
"disable": false
}
}
},
"Consumer": {
"username": "test",
"desc": "desc",
"plugins": {
"basic-auth": {
"disable": false,
"password": "test",
"username": "test"
}
}
}
}
1 change: 1 addition & 0 deletions web/cypress/fixtures/selector.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"pluginCard": ".ant-card",
"pageContent": ".ant-pro-page-container",

"tableBody":".ant-table-tbody",
"tableCell": ".ant-table-cell",
"empty": ".ant-empty-normal",
"refresh": ".anticon-reload",
Expand Down
96 changes: 96 additions & 0 deletions web/cypress/integration/rawDataEditor/test-rawDataEditor.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* 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('Test RawDataEditor', () => {
const timeout = 1000;

beforeEach(() => {
cy.login();

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

it('should create and update with rawDataEditor', function () {
const menuList = ['Route', 'Service', 'Upstream', 'Consumer'];
const publicData = this.data;
const dateset = this.dataset;
const domSelector = this.domSelector;
menuList.forEach(function (item) {
cy.visit('/');
cy.contains(item).click();
cy.contains('Create with Editor').click();
const data = dateset[item];

// create with editor
cy.window().then(({ codemirror }) => {
if (codemirror) {
codemirror.setValue(JSON.stringify(data));
}
cy.get(domSelector.drawer).should('exist');
cy.get(domSelector.drawer, { timeout }).within(() => {
cy.contains('Submit').click({
force: true,
});
cy.get(domSelector.drawer).should('not.exist');
});
});

cy.reload();
// update with editor
cy.contains(item === 'Consumer' ? data.username : data.name)
.siblings()
.contains('View')
.click();

cy.window().then(({ codemirror }) => {
if (codemirror) {
if (item === 'Consumer') {
codemirror.setValue(JSON.stringify({ ...data, desc: 'newDesc' }));
} else {
codemirror.setValue(JSON.stringify({ ...data, name: 'newName' }));
}
}
cy.get(domSelector.drawer).should('exist');
cy.get(domSelector.drawer, { timeout }).within(() => {
cy.contains('Submit').click({
force: true,
});
cy.get(domSelector.drawer).should('not.exist');
});
});

cy.reload();
cy.get(domSelector.tableBody).should('contain', item === 'Consumer' ? 'newDesc' : 'newName');

// delete resource
cy.contains(item === 'Consumer' ? 'newDesc' : 'newName')
.siblings()
.contains('Delete')
.click();
cy.contains('button', 'Confirm').click();

cy.get(domSelector.notification).should('contain', publicData[`delete${item}Success`]);
cy.get(domSelector.notificationClose).should('be.visible').click({
force: true,
multiple: true,
});
});
});
});
124 changes: 116 additions & 8 deletions web/src/components/RawDataEditor/RawDataEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,97 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useRef } from 'react';
import { Button, Drawer, PageHeader, Tooltip, notification } from 'antd';
import React, { useEffect, useRef, useState } from 'react';
import { Button, Drawer, PageHeader, notification, Space, Select } from 'antd';
import { LinkOutlined } from '@ant-design/icons';
import CodeMirror from '@uiw/react-codemirror';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { useIntl } from 'umi';
import { js_beautify } from 'js-beautify';

import { json2yaml, yaml2json } from '../../helpers';

type Props = {
visible: boolean,
readonly: boolean,
type: 'route' | 'service' | 'consumer' | 'upstream'
data: Record<string, any>,
onClose?: () => void;
onSubmit?: (data: Record<string, any>) => void;
};

const RawDataEditor: React.FC<Props> = ({ visible, readonly = true, type, data = {}, onClose }) => {
enum codeMirrorModeList {
Json = 'Json',
Yaml = 'Yaml',
}

const RawDataEditor: React.FC<Props> = ({ visible, readonly = true, type, data = {}, onClose = () => { }, onSubmit = () => { } }) => {
const ref = useRef<any>(null);
const { formatMessage } = useIntl();
const [codeMirrorMode, setCodeMirrorMode] = useState<PluginComponent.CodeMirrorMode>(
codeMirrorModeList.Json,
);

useEffect(() => {
setCodeMirrorMode(codeMirrorModeList.Json);
}, [visible])

const modeOptions = [
{ label: codeMirrorModeList.Json, value: codeMirrorModeList.Json },
{ label: codeMirrorModeList.Yaml, value: codeMirrorModeList.Yaml },
];

const handleModeChange = (value: PluginComponent.CodeMirrorMode) => {
switch (value) {
case codeMirrorModeList.Json: {
const { data: yamlData, error } = yaml2json(ref.current.editor.getValue(), true);

if (error) {
notification.error({
message: 'Invalid Yaml data',
});
return;
}
ref.current.editor.setValue(
js_beautify(yamlData, {
indent_size: 2,
}),
);
break;
}
case codeMirrorModeList.Yaml: {
const { data: jsonData, error } = json2yaml(ref.current.editor.getValue());

if (error) {
notification.error({
message: 'Invalid Json data',
});
return;
}
ref.current.editor.setValue(jsonData);
break;
}
default:
break;
}
setCodeMirrorMode(value);
};

const formatCodes = () => {
try {
if (ref.current) {
ref.current.editor.setValue(
js_beautify(ref.current.editor.getValue(), {
indent_size: 2,
}),
);
}
} catch (error) {
notification.error({
message: 'Format failed',
});
}
};

return (
<>
Expand All @@ -41,6 +114,34 @@ const RawDataEditor: React.FC<Props> = ({ visible, readonly = true, type, data =
width={700}
visible={visible}
onClose={onClose}
footer={
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Button onClick={onClose} key={1}>
{formatMessage({ id: 'component.global.cancel' })}
</Button>
<Space>
<Button
key={2}
type="primary"
onClick={() => {
try {
const editorData =
codeMirrorMode === codeMirrorModeList.Json
? JSON.parse(ref.current?.editor.getValue())
: yaml2json(ref.current?.editor.getValue(), false).data;
onSubmit(editorData);
} catch (error) {
notification.error({
message: 'Invalid JSON data',
});
}
}}
>
{formatMessage({ id: 'component.global.submit' })}
</Button>
</Space>
</div>
}
>
<PageHeader
title=""
Expand All @@ -57,11 +158,18 @@ const RawDataEditor: React.FC<Props> = ({ visible, readonly = true, type, data =
>
Document
</Button>,
<Tooltip placement="top" title={formatMessage({ id: 'component.rawDataEditor.tip' })}>
<Button type="primary" onClick={() => { }} key={2}>
Format
</Button>
</Tooltip>,
<Select
defaultValue={codeMirrorModeList.Json}
value={codeMirrorMode}
options={modeOptions}
onChange={(value: PluginComponent.CodeMirrorMode) => {
handleModeChange(value);
}}
data-cy='code-mirror-mode'
></Select>,
<Button type="primary" onClick={formatCodes} key={2}>
Format
</Button>,
<CopyToClipboard text={JSON.stringify(data)} onCopy={(_: string, result: boolean) => {
if (!result) {
notification.error({
Expand Down
2 changes: 2 additions & 0 deletions web/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ export const codeMessage = {
};

export const DEFAULT_GLOBAL_RULE_ID = '1';

export const DELETE_FIELDS = ['id', 'create_time', 'update_time'];
1 change: 1 addition & 0 deletions web/src/locales/en-US/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default {
'component.global.cancel': 'Cancel',
'component.global.submit': 'Submit',
'component.global.create': 'Create',
'component.global.createWithEditor': 'Create with Editor',
'component.global.add': 'Add',
'component.global.save': 'Save',
'component.global.edit': 'Edit',
Expand Down
1 change: 1 addition & 0 deletions web/src/locales/zh-CN/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default {
'component.global.cancel': '取消',
'component.global.submit': '提交',
'component.global.create': '创建',
'component.global.createWithEditor': '使用编辑器创建',
'component.global.add': '新建',
'component.global.save': '保存',
'component.global.edit': '编辑',
Expand Down
Loading

0 comments on commit 548bbe3

Please sign in to comment.