feat: api test support execute in different tab

This commit is contained in:
scarqin 2022-08-10 16:05:11 +08:00
parent 4e66601b57
commit 91dabd30fc
10 changed files with 81 additions and 89 deletions

View File

@ -1,7 +1,7 @@
{
"name": "eoapi",
"souceLocale": "zh-Hans",
"version": "1.4.0-beta",
"version": "1.5.0",
"main": "out/app/electron-main/main.js",
"description": "A lightweight, extensible API tool",
"homepage": "https://github.com/eolinker/eoapi.git",

View File

@ -174,6 +174,10 @@ export class ApiTabService {
if (replaceTab.hasChanged) {
replaceTab.isFixed = true;
}
//Has tested set fixed
if(currentTab.pathname === '/home/api/test'&&model.testStartTime!==undefined){
replaceTab.isFixed = true;
}
// Set storage
//Set baseContent
@ -186,7 +190,7 @@ export class ApiTabService {
replaceTab.content = inData.when === 'saved' ? {} : currentTab.content || {};
replaceTab.content[contentID] = model && !isEmptyObj(model) ? model : null;
}
// console.log('updatePartialTab', currentTab.uuid, replaceTab);
console.log('updatePartialTab', currentTab.uuid, replaceTab);
this.apiTabComponent.updatePartialTab(inData.url, replaceTab);
}
/**
@ -195,7 +199,7 @@ export class ApiTabService {
*
* @param inData.url get component fit tab data
*/
afterContentChanged(inData: { when: 'init' | 'editing' | 'saved'; url: string; model: any }) {
afterContentChanged(inData: { when: 'init' | 'editing' |'saved'; url: string; model: any }) {
if (!this.apiTabComponent) {
console.warn(`EO_WARNING:apiTabComponent hasn't init yet!`);
return;

View File

@ -68,7 +68,7 @@
</div>
<div class="content_container {{ this.id ? 'has_tab_page' : '' }}">
<!-- Manually pick pageID,Because pageID always change -->
<nz-tabset class="inside_page_tab" *ngIf="this.id" nzLinkRouter>
<nz-tabset class="inside_page_tabset" *ngIf="this.id" nzLinkRouter>
<nz-tab *ngFor="let tab of TABS">
<a
*nzTabLink

View File

@ -145,28 +145,15 @@ export class ApiTabOperateService {
* @param tab
* @returns
*/
getTabIndex(type, tab: TabItem): number {
getSameContentTabIndex(tab: TabItem): number {
let result = -1;
//If exist tabreturn exist TabIndex
if (this.tabStorage.tabsByID.get(tab.uuid)) {
return this.tabStorage.tabOrder.findIndex((uuid) => uuid === tab.uuid);
}
const mapObj = Object.fromEntries(this.tabStorage.tabsByID);
for (const key in mapObj) {
if (Object.prototype.hasOwnProperty.call(mapObj, key)) {
const tabInfo = mapObj[key];
if (tabInfo.params.uuid && tabInfo.params.uuid === tab.params.uuid) {
if (type === 'sameContent') {
if (tabInfo.pathname === tab.pathname) {
result = this.tabStorage.tabOrder.findIndex((uuid) => uuid === tabInfo.uuid);
break;
}
} else {
result = this.tabStorage.tabOrder.findIndex((uuid) => uuid === tabInfo.uuid);
}
if (tabInfo.params.uuid && tabInfo.params.uuid === tab.params.uuid && tabInfo.pathname === tab.pathname) {
result = this.tabStorage.tabOrder.findIndex((uuid) => uuid === tabInfo.uuid);
break;
//TODO how to replace current exist tab but editing
}
}
}
@ -207,26 +194,6 @@ export class ApiTabOperateService {
});
return result as TabItem;
}
formatUrl(url) {
const urlArr = url.split('?');
// Parse query params
const params = {};
new URLSearchParams(urlArr[1]).forEach((value, key) => {
if (key === 'pageID') {
params[key] = Number(value);
return;
}
params[key] = value;
});
return (
urlArr[0] +
'?' +
Object.keys(params)
.sort()
.map((keyName) => `${keyName}=${params[keyName]}`)
.join('&')
);
}
/**
* Operate tab after router change,router triggle tab change
* Such as new tab,pick tab,close tab...
@ -235,19 +202,14 @@ export class ApiTabOperateService {
*/
operateTabAfterRouteChange(res: { url: string }) {
const tmpTabItem = this.getBaiscTabFromUrl(res.url);
const sameContentIndex = this.getTabIndex('sameContent', tmpTabItem);
const sameContentIndex = this.getSameContentTabIndex(tmpTabItem);
const existTab = this.getTabByIndex(sameContentIndex);
const samePageID = this.tabStorage.tabsByID.has(tmpTabItem.uuid);
console.log('operateTabAfterRouteChange', existTab, tmpTabItem);
//If page repeat or final url is different(lack of id/additional params)
//jump to exist tab item to keep same pageID and so on
//If page lack pageID
//Jump to exist tab item to keep same pageID and so on
if (!res.url.includes('pageID')) {
if (existTab) {
if (existTab.pathname !== tmpTabItem.pathname) {
tmpTabItem.uuid = tmpTabItem.params.pageID = Date.now();
} else {
tmpTabItem.uuid = tmpTabItem.params.pageID = existTab.uuid;
}
tmpTabItem.uuid = tmpTabItem.params.pageID = existTab.uuid;
}
this.navigateTabRoute(tmpTabItem);
return;
@ -260,7 +222,7 @@ export class ApiTabOperateService {
}
//same tab content,selected it
if (existTab && this.getUrlByTab(existTab) === this.formatUrl(res.url)) {
if (existTab) {
this.selectedIndex = sameContentIndex;
this.updateChildView();
return;
@ -282,8 +244,8 @@ export class ApiTabOperateService {
}
}
}
//If has same tab (same uuid),replace it
const samePageID = this.tabStorage.tabsByID.has(tmpTabItem.uuid);
if (samePageID) {
this.selectedIndex = this.tabStorage.tabOrder.findIndex((uuid) => uuid === tmpTabItem.uuid);
this.tabStorage.updateTab(this.selectedIndex, tmpTabItem);

View File

@ -1,4 +1,4 @@
{{getConsoleTabs()|json}}
<!-- {{getConsoleTabs()|json}} -->
<nz-tabset
[(nzSelectedIndex)]="tabOperate.selectedIndex"
nzType="editable-card"

View File

@ -116,7 +116,7 @@ export class ApiTabComponent implements OnInit, OnDestroy {
*/
getTabByUrl(url: string): TabItem | null {
const tabItem = this.tabOperate.getBaiscTabFromUrl(url);
const existTabIndex = this.tabOperate.getTabIndex('sameTab', tabItem);
const existTabIndex = this.tabOperate.getSameContentTabIndex(tabItem);
if (existTabIndex === -1) {
return null;
}

View File

@ -13,7 +13,7 @@ import {
import { MessageService } from '../../../shared/services/message';
import { interval, Subscription, Observable, Subject } from 'rxjs';
import { take, takeUntil, distinctUntilChanged } from 'rxjs/operators';
import { takeUntil, distinctUntilChanged } from 'rxjs/operators';
import { TestServerService } from '../../../shared/services/api-test/test-server.service';
import { ApiTestUtilService } from './api-test-util.service';
@ -39,6 +39,7 @@ import { AnyRecord } from 'dns';
const API_TEST_DRAG_TOP_HEIGHT_KEY = 'API_TEST_DRAG_TOP_HEIGHT';
interface testViewModel {
request: ApiTestData;
testStartTime?: number;
testResult: {
request: any;
response: any;
@ -77,14 +78,15 @@ export class ApiTestComponent implements OnInit, OnDestroy {
status: 'start' | 'testing' | 'tested' = 'start';
waitSeconds = 0;
responseTabIndexRes = 0;
initTimes = 0;
isRequestBodyLoaded = false;
initHeight = localStorage.getItem(API_TEST_DRAG_TOP_HEIGHT_KEY) || '45%';
testServer: TestServerLocalNodeService | TestServerServerlessService | TestServerRemoteService;
REQUEST_METHOD = objectToArray(RequestMethod);
REQUEST_PROTOCOL = objectToArray(RequestProtocol);
MAX_TEST_SECONDS = 60;
private initTimes = 0;
private status$: Subject<string> = new Subject<string>();
private timer$: Subscription;
private destroy$: Subject<void> = new Subject<void>();
@ -145,10 +147,20 @@ export class ApiTestComponent implements OnInit, OnDestroy {
//!Prevent await async ,replace current api data
if (initTimes >= this.initTimes) {
this.model.request = requestInfo;
}else{
} else {
return;
}
this.initContentType();
this.waitSeconds = 0;
this.status = 'start';
} else {
if (this.timer$ && this.model.testStartTime) {
this.waitSeconds = Math.round((Date.now() - this.model.testStartTime) / 1000);
this.status$.next('testing');
} else {
this.waitSeconds = 0;
this.status$.next('start');
}
}
this.initBasicForm();
//! Set this two function to reset form
@ -236,12 +248,12 @@ export class ApiTestComponent implements OnInit, OnDestroy {
if (!this.initialModel.request || !this.model.request) {
return false;
}
console.log(
'api test origin:',
this.apiTestUtil.formatEditingApiData(this.initialModel.request),
'after:',
this.apiTestUtil.formatEditingApiData(this.model.request)
);
// console.log(
// 'api test origin:',
// this.apiTestUtil.formatEditingApiData(this.initialModel.request),
// 'after:',
// this.apiTestUtil.formatEditingApiData(this.model.request)
// );
const originText = JSON.stringify(this.apiTestUtil.formatEditingApiData(this.initialModel.request));
const afterText = JSON.stringify(this.apiTestUtil.formatEditingApiData(this.model.request));
if (originText !== afterText) {
@ -298,7 +310,7 @@ export class ApiTestComponent implements OnInit, OnDestroy {
}
private test() {
this.testServer.send('unitTest', {
// id: this.apiTab.tabID,
id: this.route.snapshot.queryParams.pageID,
action: 'ajax',
data: this.testServer.formatRequestData(this.model.request, {
env: this.env,
@ -308,11 +320,12 @@ export class ApiTestComponent implements OnInit, OnDestroy {
lang: this.lang.systemLanguage === 'zh-Hans' ? 'cn' : 'en',
}),
});
this.model.testStartTime = Date.now();
this.status$.next('testing');
}
private abort() {
this.testServer.send('unitTest', {
// id: this.apiTab.tabID,
id: this.route.snapshot.queryParams.pageID,
action: 'abort',
});
this.status$.next('tested');
@ -332,7 +345,6 @@ export class ApiTestComponent implements OnInit, OnDestroy {
response: message.response,
};
this.model.testResult = tmpHistory;
this.modelChange.emit(this.model);
this.status$.next('tested');
if (message.status === 'error') {
return;
@ -349,6 +361,24 @@ export class ApiTestComponent implements OnInit, OnDestroy {
// TODO Other tab test finish,support multiple tab test same time
this.addHistory(message.history, this.model.request.uuid);
}
setTestSecondsTimmer() {
if (this.timer$) {
this.timer$.unsubscribe();
}
const that = this;
this.timer$ = interval(1000)
.subscribe({
next(val) {
that.waitSeconds++;
if (that.waitSeconds >= that.MAX_TEST_SECONDS) {
this.complete();
}
},
complete() {
that.changeStatus('tested');
},
});
}
/**
* Change test status
*
@ -356,23 +386,15 @@ export class ApiTestComponent implements OnInit, OnDestroy {
*/
private changeStatus(status) {
this.status = status;
const that = this;
console.log('changeStatus',status);
switch (status) {
case 'testing': {
this.timer$ = interval(1000)
.pipe(take(60))
.subscribe({
next(val) {
console.log('next');
that.waitSeconds = val + 1;
},
complete() {
that.changeStatus('tested');
},
});
this.setTestSecondsTimmer();
break;
}
case 'tested': {
this.model.testStartTime = 0;
this.modelChange.emit(this.model);
this.timer$.unsubscribe();
this.waitSeconds = 0;
this.responseTabIndexRes = 0;

View File

@ -18,7 +18,7 @@ export class TestServerRemoteService implements TestServer {
break;
}
default: {
this.xhrByTabID[message.id].abort();
this.xhrByTabID[message.id]?.abort();
}
}
if (message.action !== 'ajax') {return;}

View File

@ -20,7 +20,7 @@ export class TestServerServerlessService implements TestServer {
break;
}
default: {
this.xhrByTabID[message.id].abort();
this.xhrByTabID[message.id]?.abort();
}
}
if (message.action !== 'ajax') {return;}

View File

@ -129,7 +129,7 @@
<context context-type="sourcefile">src/app/pages/api/api-tab.service.ts</context>
<context context-type="linenumber">19</context>
</context-group>
<target state="translated"> API</target>
<target state="translated">添加 API</target>
</trans-unit>
<trans-unit id="1295614462098694869" datatype="html">
<source>Preview</source>
@ -430,7 +430,7 @@
<context context-type="sourcefile">src/app/pages/api/detail/api-detail-util.service.ts</context>
<context context-type="linenumber">154</context>
</context-group>
<target state="translated">&lt;button type="button" class="eo-operate-btn" ng-click="$ctrl.data.isSpreedBtnClick=!$ctrl.data.isSpreedBtnClick;$ctrl.data.isSpreed=true;$ctrl.mainObject.baseFun.spreedAll($event);$ctrl.data.isSpreed=false;"&gt;{{$ctrl.data.isSpreedBtnClick?"全部展开":"全部收缩"}}&lt;/button&gt;</target>
<target state="translated">&lt;button type="button" class="eo-operate-btn" ng-click="$ctrl.data.isSpreedBtnClick=!$ctrl.data.isSpreedBtnClick;$ctrl.data.isSpreed=true;$ctrl.mainObject.baseFun.spreedAll($event);$ctrl.data.isSpreed=false;"&gt;{{$ctrl.data.isSpreedBtnClick?"全部收缩":"全部展开"}}&lt;/button&gt;</target>
</trans-unit>
<trans-unit id="5531234025897938404" datatype="html">
<source>&lt;span class=&quot;eo-operate-btn fs12&quot; ng-show=&quot;item.minimum ||
@ -446,7 +446,7 @@
item.maximum ||
item.minLength ||
item.maxLength ||
(item.enum &amp;&amp; item.enum.length &gt; 0 &amp;&amp; item.enum[0].value)"&gt;{{item.isClick?"展开":"收缩"}}&lt;/span&gt;</target>
(item.enum &amp;&amp; item.enum.length &gt; 0 &amp;&amp; item.enum[0].value)"&gt;{{item.isClick?"收缩":"展开"}}&lt;/span&gt;</target>
</trans-unit>
<trans-unit id="9b985691961fb794ada1fb16421cc0c677c6480a" datatype="html">
<source>Request Headers</source>
@ -526,7 +526,7 @@
<context context-type="sourcefile">src/app/pages/api/detail/api-detail.component.html</context>
<context context-type="linenumber">32</context>
</context-group>
<target state="needs-translation">The outermost structure is: <x id="INTERPOLATION" equiv-text="{{ CONST.JSON_ROOT_TYPE[model.requestBodyJsonType] }}"/></target>
<target state="translated">JSON 根类型: <x id="INTERPOLATION" equiv-text="{{ CONST.JSON_ROOT_TYPE[model.requestBodyJsonType] }}"/></target>
</trans-unit>
<trans-unit id="a0b40184f4ad0e2da08dabf8c5209d23f02513b7" datatype="html">
<source>Response Headers</source>
@ -574,7 +574,7 @@
<context context-type="sourcefile">src/app/pages/api/detail/api-detail.component.html</context>
<context context-type="linenumber">54</context>
</context-group>
<target state="needs-translation">The outermost structure is: <x id="INTERPOLATION" equiv-text="{{ CONST.JSON_ROOT_TYPE[model.responseBodyJsonType] }}"/></target>
<target state="translated">JSON 根类型: <x id="INTERPOLATION" equiv-text="{{ CONST.JSON_ROOT_TYPE[model.responseBodyJsonType] }}"/></target>
</trans-unit>
<trans-unit id="150b08e70491356590c3f3706be1715d0b099467" datatype="html">
<source>MOCK</source>
@ -894,7 +894,7 @@
<context context-type="sourcefile">src/app/pages/api/edit/api-edit.component.ts</context>
<context context-type="linenumber">138</context>
</context-group>
<target state="needs-translation">Failed Operation</target>
<target state="translated">操作失败</target>
</trans-unit>
<trans-unit id="4926605800185679045" datatype="html">
<source>Root directory</source>
@ -926,7 +926,7 @@
<context context-type="sourcefile">src/app/pages/api/edit/extra-setting/api-params-extra-setting.component.ts</context>
<context context-type="linenumber">45</context>
</context-group>
<target state="needs-translation">Minimum length</target>
<target state="translated">最小长度</target>
</trans-unit>
<trans-unit id="1834980375662449209" datatype="html">
<source>Maximum Length</source>
@ -934,7 +934,7 @@
<context context-type="sourcefile">src/app/pages/api/edit/extra-setting/api-params-extra-setting.component.ts</context>
<context context-type="linenumber">53</context>
</context-group>
<target state="needs-translation">Maximum Length</target>
<target state="translated">最大长度</target>
</trans-unit>
<trans-unit id="1571736586383450200" datatype="html">
<source>Minimum</source>
@ -1126,7 +1126,9 @@
<context context-type="sourcefile">src/app/pages/api/mock/api-mock.component.html</context>
<context context-type="linenumber">20,21</context>
</context-group>
<target state="translated"> <x id="INTERPOLATION" equiv-text="{{ scope.createWay === 'system' ? '预览' : '编辑' }}"/></target>
<target state="translated">
<x id="INTERPOLATION" equiv-text="{{ scope.createWay === 'system' ? '预览' : '编辑' }}"/>
</target>
</trans-unit>
<trans-unit id="feacc7017fd641efba1aeb4883e42a352d9d5b6c" datatype="html">
<source>Are you sure you want to delete this Mock?</source>
@ -3220,7 +3222,9 @@
<context context-type="sourcefile">src/app/shared/components/setting/common/extensions.component.ts</context>
<context context-type="linenumber">40,42</context>
</context-group>
<target state="translated"><x id="INTERPOLATION" equiv-text="{{ module.properties[field]?.placeholder ?? '请输入 ' + module.properties[field]?.label }}"/></target>
<target state="translated">
<x id="INTERPOLATION" equiv-text="{{ module.properties[field]?.placeholder ?? '请输入 ' + module.properties[field]?.label }}"/>
</target>
</trans-unit>
<trans-unit id="e0d1ece694da029a20f5a97fbf3302f6213da891" datatype="html">
<source>No plugins are currently installed,<x id="START_LINK" ctype="x-a" equiv-text="&lt;a class=&quot;eo_link&quot; (click)=&quot;navToExtensionList()&quot;&gt;"/> go to install <x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/></source>