diff --git a/package.json b/package.json index e4b01d133..bf9192415 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "arex", "private": true, - "version": "0.6.9", + "version": "0.6.10", "description": "", "homepage": "https://github.com/arextest/arex", "main": "index.js", diff --git a/packages/arex-request/package.json b/packages/arex-request/package.json index 8256b8b76..ce8cf6d09 100644 --- a/packages/arex-request/package.json +++ b/packages/arex-request/package.json @@ -1,6 +1,6 @@ { "name": "@arextest/arex-request", - "version": "0.3.3", + "version": "0.3.4", "type": "module", "main": "dist/arex-request.js", "module": "dist/arex-request.js", diff --git a/packages/arex-request/src/components/Request/PreRequestScript.tsx b/packages/arex-request/src/components/Request/PreRequestScript.tsx index 091e6a9c8..fbe5b786d 100644 --- a/packages/arex-request/src/components/Request/PreRequestScript.tsx +++ b/packages/arex-request/src/components/Request/PreRequestScript.tsx @@ -9,17 +9,6 @@ import { preTestCodeSnippet } from './snippets'; const { Text } = Typography; -export const ResponseTestHeader = styled.div` - display: flex; - justify-content: space-between; - & > span:first-of-type { - font-size: 13px; - line-height: 32px; - font-weight: 500; - color: #9d9d9d; - } -`; - export const ResponseTestWrapper = styled.div` overflow-y: auto; display: flex; @@ -56,9 +45,8 @@ const PreRequestScript = () => { flex-direction: column; `} > - <ResponseTestHeader> - <span>{t('preRequest.javascript_code')}</span> - </ResponseTestHeader> + <Typography.Text type='secondary'>{t('preRequest.javascript_code')}</Typography.Text> + <ResponseTestWrapper> <div css={css` diff --git a/packages/arex-request/src/components/Request/RequestTests.tsx b/packages/arex-request/src/components/Request/RequestTests.tsx index 9eb09bd2c..4381d84fd 100644 --- a/packages/arex-request/src/components/Request/RequestTests.tsx +++ b/packages/arex-request/src/components/Request/RequestTests.tsx @@ -8,27 +8,17 @@ import { useArexRequestStore } from '../../hooks'; import { testCodeSnippet } from './snippets'; const { Text } = Typography; -export const RequestTestHeader = styled.div` - display: flex; - justify-content: space-between; - & > span:first-of-type { - font-size: 13px; - line-height: 32px; - font-weight: 500; - color: #9d9d9d; - } -`; export const RequestTestWrapper = styled.div` overflow-y: auto; + overflow-x: hidden; display: flex; justify-content: space-between; flex: 1; & > div:last-of-type { width: 35%; text-align: left; - //border-left: 1px solid #eee; - padding-left: 20px; + padding-left: 8px; } `; @@ -66,16 +56,13 @@ const RequestTests = () => { flex-direction: column; `} > - <RequestTestHeader> - <span>{t('preRequest.javascript_code')}</span> - </RequestTestHeader> + <Typography.Text type='secondary'>{t('preRequest.javascript_code')}</Typography.Text> <RequestTestWrapper> <div css={css` min-width: 0; flex: 1; - //width: 100%; `} > <Editor @@ -99,47 +86,31 @@ const RequestTests = () => { flex-direction: column; `} > - <Text - type={'secondary'} - css={css` - margin-bottom: 4px; - `} - > + <Text type={'secondary'}> Test scripts are written in JavaScript, and are run after the response is received. </Text> - <div> - <a - type='text' - onClick={() => - window.open('https://learning.postman.com/docs/writing-scripts/test-scripts/') - } - style={{ marginLeft: '8px' }} - > - Read documentation - </a> - </div> - <Text - type={'secondary'} - css={css` - padding: 16px 0; - `} - > - Snippets - </Text> - <div - css={css` - overflow: auto; - flex: 1; - `} + <a + type='text' + rel='noreferrer' + target={'_blank'} + href={'https://learning.postman.com/docs/writing-scripts/test-scripts/'} + style={{ marginLeft: '8px' }} > - {codeSnippet.map((e, i) => ( + Read documentation + </a> + + <br /> + + <Text type={'secondary'}>Snippets</Text> + <div> + {codeSnippet.map((snippet, i) => ( <ThemeColorPrimaryButton key={i} size='small' type='text' - onClick={() => addTest(e.text)} + onClick={() => addTest(snippet.text)} > - {e.name} + {snippet.name} </ThemeColorPrimaryButton> ))} </div> diff --git a/packages/arex/package.json b/packages/arex/package.json index ad451e5ee..a05b0da4f 100644 --- a/packages/arex/package.json +++ b/packages/arex/package.json @@ -1,7 +1,7 @@ { "name": "arex", "private": true, - "version": "0.6.9", + "version": "0.6.10", "author": "arextest", "main": "dist-electron/main.js", "files": [ diff --git a/packages/arex/src/i18n/locales/cn/common.json b/packages/arex/src/i18n/locales/cn/common.json index 58c50041e..addc6408a 100644 --- a/packages/arex/src/i18n/locales/cn/common.json +++ b/packages/arex/src/i18n/locales/cn/common.json @@ -78,5 +78,8 @@ "quickPick": "快速选择", "1d": "近一天" }, - "checkAll": "选择全部" + "checkAll": "选择全部", + "advancedOptions": "高级选项", + "and": "和", + "or": "或" } diff --git a/packages/arex/src/i18n/locales/cn/components.json b/packages/arex/src/i18n/locales/cn/components.json index 7243b28d8..99aa77ff2 100644 --- a/packages/arex/src/i18n/locales/cn/components.json +++ b/packages/arex/src/i18n/locales/cn/components.json @@ -83,15 +83,18 @@ "emptyCaseRange": "回放范围不能为空", "emptyStartTime": "Case 开始时间不能为空", "emptyEndTime": "Case 结束时间不能为空", + "planName": "回放名称", + "planNamePlaceholder": "可选,默认为应用名称 + 创建时间", "paths": "回放路径", "pathsTooltip": "选择需要回放的路径。如果不指定,默认情况下,所有路径下的用例都将被回放。", - "replayReportName": "任务名称", + "pathsPlaceholder": "可选,默认选择所有路径", "state": "状态", "all": "全部", "passed": "成功", "failed": "失败", "invalid": "无效", "blocked": "待执行", + "queued": "队列中", "executor": "执行人", "replayStartTime": "开始时间", "report": "报告", @@ -112,7 +115,7 @@ "denoise": "对比配置推荐", "noDenoiseRecommended": "暂无对比配置推荐", "logs": "执行日志", - "retry": "重新计算", + "retry": "失败重试", "rerun": "重新执行", "rerunError": "重新执行(仅错误类型)", "rerunTip": "确定重新执行该回放?", @@ -298,6 +301,7 @@ "maxQPS": "最大QPS", "QPSTips": "有效范围 1 - 20", "emptyQPS": "请输入最大QPS", + "skipMock": "不Mock依赖", "sync": "同步", "emptyContractTip": "无报文契约,请尝试同步更新契约", "contract": "契约", diff --git a/packages/arex/src/i18n/locales/en/common.json b/packages/arex/src/i18n/locales/en/common.json index 25f691f26..9dfd8d189 100644 --- a/packages/arex/src/i18n/locales/en/common.json +++ b/packages/arex/src/i18n/locales/en/common.json @@ -81,5 +81,8 @@ "quickPick": "Quick Pick", "1d": "1d" }, - "checkAll": "Check All" + "checkAll": "Check All", + "advancedOptions": "Advanced Options", + "and": " and", + "or": " or" } diff --git a/packages/arex/src/i18n/locales/en/components.json b/packages/arex/src/i18n/locales/en/components.json index 7e645ea9f..add288a6c 100644 --- a/packages/arex/src/i18n/locales/en/components.json +++ b/packages/arex/src/i18n/locales/en/components.json @@ -83,15 +83,19 @@ "emptyCaseRange": "Case range can't be empty", "emptyStartTime": "Start Time can't be empty", "emptyEndTime": "End Time can't be empty", + "planName": "Name", + "planNamePlaceholder": "Optional, defaults to app name + creation time", "paths": "Paths", "pathsTooltip": "Select the paths for which cases will be replayed. If not specified, all paths will be replayed by default.", - "replayReportName": "Report", + "pathsPlaceholder": "Optional, all paths are selected by default", + "replayReportName": " Report Name", "state": "State", "all": "All", "passed": "Passed", "failed": "Failed", "invalid": "Invalid", "blocked": "Blocked", + "queued": "Queued", "executor": "Executor", "replayStartTime": "Replay Start Time", "report": "Report", @@ -99,7 +103,7 @@ "replayPassRate": "Replay Pass Rate", "passRate": "Pass Rate", "apiPassRate": "API Pass Rate", - "reportName": "Report Name", + "reportName": " Report Name", "reportId": "Report ID", "recordVersion": "Record version", "replayVersion": "Replay version", @@ -177,7 +181,7 @@ "escExit": "Press Esc to exit", "addIgnoreSuccess": "Ignore node successfully", "diffMatch": "Diff Match", - "replayEndTime": "ReplayEndTime", + "replayEndTime": "Replay End Time", "operationName": "Operation Name", "recordedCaseCount": "Case Count", "recordTime": "Record Time", @@ -299,6 +303,7 @@ "maxQPS": "Max QPS", "QPSTips": "Allowable value range 1 - 20", "emptyQPS": "Please input your max QPS", + "skipMock": "Skip Mock", "sync": "Sync", "emptyContractTip": "No contract, please try to synchronize the contract", "contract": "Contract", diff --git a/packages/arex/src/panes/AppSetting/Replay/index.tsx b/packages/arex/src/panes/AppSetting/Replay/index.tsx index 25de8a9fe..d768ef67c 100644 --- a/packages/arex/src/panes/AppSetting/Replay/index.tsx +++ b/packages/arex/src/panes/AppSetting/Replay/index.tsx @@ -84,22 +84,10 @@ const SettingReplay: React.FC<SettingRecordProps> = ({ appId }) => { <InputNumber min={1} max={20} precision={0} /> </Form.Item> - <Form.Item - label={ - <HelpTooltip title={t('appSetting.caseRangeTooltip')}> - {t('appSetting.caseRange')} - </HelpTooltip> - } - name='offsetDays' - rules={[{ required: true, message: t('appSetting.emptyCaseRange') as string }]} - > - <InputNumber min={1} /> - </Form.Item> - <Form.Item label={ <HelpTooltip title={t('appSetting.exclusionTooltip')}> - {t('appSetting.exclusion')} + {t('appSetting.skipMock')} </HelpTooltip> } name='excludeOperationMap' diff --git a/packages/arex/src/panes/Replay/AppTitle.tsx b/packages/arex/src/panes/Replay/AppTitle.tsx index ff60ccbe7..09066d65e 100644 --- a/packages/arex/src/panes/Replay/AppTitle.tsx +++ b/packages/arex/src/panes/Replay/AppTitle.tsx @@ -25,6 +25,7 @@ import { AutoComplete, Badge, Button, + Collapse, DatePicker, Form, Input, @@ -36,16 +37,7 @@ import { Typography, } from 'antd'; import dayjs, { Dayjs } from 'dayjs'; -import React, { - createElement, - FC, - ReactNode, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import React, { createElement, FC, ReactNode, useCallback, useMemo, useRef, useState } from 'react'; import { EMAIL_KEY, PanesType, TARGET_HOST_AUTOCOMPLETE_KEY } from '@/constant'; import { useNavPane } from '@/hooks'; @@ -64,6 +56,7 @@ type AppTitleProps = { }; type CreatePlanForm = { + planName?: string; targetEnv: string; caseSourceRange: [Dayjs, Dayjs]; operationList?: string[]; @@ -81,13 +74,7 @@ const TitleWrapper = styled( onRefresh?: () => void; onSetting?: () => void; }) => { - const navPane = useNavPane(); const { t } = useTranslation(['components']); - const [planId, setPlanId] = useState<string>(); - - useEffect(() => { - setPlanId(props.planId); - }, [props.planId]); return ( <div id='arex-replay-record-detail-btn' className={props.className}> @@ -141,29 +128,6 @@ const TitleWrapper = styled( onClick={props.onSetting} /> )} - - <Input.Search - allowClear - size='small' - value={planId} - placeholder={t('replay.searchForPlanId') as string} - onChange={(e) => { - setPlanId(e.target.value); - !e.target.value && - navPane({ - id: props.appId, - type: PanesType.REPLAY, - }); - }} - onSearch={(planId) => { - navPane({ - id: props.appId, - type: PanesType.REPLAY, - data: { planId: planId || undefined }, // set '' to undefined - }); - }} - style={{ display: 'inline', width: '160px', marginLeft: '8px' }} - /> </> ) : ( <Skeleton.Input active size='small' style={{ width: '200px' }} /> @@ -276,6 +240,7 @@ const AppTitle: FC<AppTitleProps> = ({ appId, sourceEnv: 'pro', targetEnv, + planName: values.planName, caseSourceFrom: values.caseSourceRange[0].valueOf(), caseSourceTo: values.caseSourceRange[1].valueOf(), operationCaseInfoList: values.operationList?.map((operationId) => ({ @@ -394,7 +359,7 @@ const AppTitle: FC<AppTitleProps> = ({ onOk={handleStartReplay} onCancel={handleCloseModal} styles={{ - body: { paddingBottom: '12px' }, + body: { padding: '8px 0' }, }} confirmLoading={confirmLoading} > @@ -462,21 +427,56 @@ const AppTitle: FC<AppTitleProps> = ({ /> </Form.Item> - <Form.Item - label={<HelpTooltip title={t('replay.pathsTooltip')}>{t('replay.paths')}</HelpTooltip>} - name='operationList' - > - <Select - mode='multiple' - maxTagCount={3} - options={interfacesOptions} - optionFilterProp='label' - /> - </Form.Item> - - <Form.Item label={'Webhook'}> - <Typography.Text copyable>{webhook}</Typography.Text> - </Form.Item> + <Collapse + css={css` + margin-top: -8px; + background-color: transparent; + .ant-collapse-content-box { + padding: 0 !important; + } + `} + bordered={false} + items={[ + { + key: 'advancedOptions', + label: ( + <Typography.Text> + {t('advancedOptions', { + ns: 'common', + })} + </Typography.Text> + ), + children: ( + <> + <Form.Item label={t('replay.planName')} name='planName'> + <Input allowClear placeholder={t('replay.planNamePlaceholder') as string} /> + </Form.Item> + + <Form.Item + label={ + <HelpTooltip title={t('replay.pathsTooltip')}> + {t('replay.paths')} + </HelpTooltip> + } + name='operationList' + > + <Select + mode='multiple' + maxTagCount={3} + placeholder={t('replay.pathsPlaceholder')} + options={interfacesOptions} + optionFilterProp='label' + /> + </Form.Item> + + <Form.Item label={'Webhook'}> + <Typography.Text copyable>{webhook}</Typography.Text> + </Form.Item> + </> + ), + }, + ]} + /> </Form> </Modal> diff --git a/packages/arex/src/panes/Replay/PlanItem.tsx b/packages/arex/src/panes/Replay/PlanItem.tsx index d5ba110b6..d0f82cfa2 100644 --- a/packages/arex/src/panes/Replay/PlanItem.tsx +++ b/packages/arex/src/panes/Replay/PlanItem.tsx @@ -3,6 +3,7 @@ import 'chart.js/auto'; import Icon, { ContainerOutlined, DeleteOutlined, + ExclamationCircleFilled, FileTextOutlined, RedoOutlined, SearchOutlined, @@ -13,12 +14,12 @@ import { ReplayLogsDrawer } from '@arextest/arex-common'; import { copyToClipboard, HighlightRowTable, + Label, SpaceBetweenWrapper, TooltipButton, useArexPaneProps, useTranslation, } from '@arextest/arex-core'; -import { css } from '@emotion/react'; import { useRequest } from 'ahooks'; import { App, @@ -41,7 +42,6 @@ import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 're import { Pie } from 'react-chartjs-2'; import CountUp from 'react-countup'; -import { StatusTag } from '@/components'; import { ResultsState } from '@/components/StatusTag'; import { PanesType } from '@/constant'; import { useNavPane } from '@/hooks'; @@ -71,11 +71,12 @@ export type ReplayPlanItemProps = { readOnly?: boolean; filter?: (record: PlanItemStatistic) => boolean; onRefresh?: () => void; + onDelete?: (planId: string) => void; }; const PlanItem: FC<ReplayPlanItemProps> = (props) => { - const { selectedPlan, filter, onRefresh } = props; - const { message } = App.useApp(); + const { selectedPlan, filter, onRefresh, onDelete } = props; + const { message, modal } = App.useApp(); const { activePane } = useMenusPanes(); const { t } = useTranslation(['components', 'common']); @@ -202,17 +203,6 @@ const PlanItem: FC<ReplayPlanItemProps> = (props) => { const searchInput = useRef<InputRef>(null); const columns = useMemo<ColumnsType<PlanItemStatistic>>(() => { const _columns: ColumnsType<PlanItemStatistic> = [ - // { - // title: t('replay.planItemID'), - // dataIndex: 'planItemId', - // key: 'planItemId', - // ellipsis: { showTitle: false }, - // render: (value) => ( - // <Tooltip placement='topLeft' title={value}> - // {value} - // </Tooltip> - // ), - // }, { title: t('replay.api'), dataIndex: 'operationName', @@ -257,26 +247,6 @@ const PlanItem: FC<ReplayPlanItemProps> = (props) => { ); }, }, - { - title: t('replay.state'), - render: (_, record) => ( - <div style={{ overflow: 'hidden' }}> - <StatusTag - status={record.status} - caseCount={record.successCaseCount + record.failCaseCount + record.errorCaseCount} - totalCaseCount={record.totalCaseCount} - message={record.errorMessage} - /> - </div> - ), - }, - { - title: t('replay.timeConsumed'), - render: (_, record) => - record.replayEndTime && record.replayStartTime - ? (record.replayEndTime - record.replayStartTime) / 1000 - : '-', - }, { title: t('replay.cases'), dataIndex: 'totalCaseCount', @@ -302,7 +272,7 @@ const PlanItem: FC<ReplayPlanItemProps> = (props) => { render: (count, record) => CaseCountRender(count, record, 2, props.readOnly), }, { - title: t('replay.blocked'), + title: t('replay.queued'), dataIndex: 'waitCaseCount', width: 72, render: (text) => ( @@ -325,7 +295,7 @@ const PlanItem: FC<ReplayPlanItemProps> = (props) => { <TooltipButton icon={<ContainerOutlined />} title={t('replay.diffScenes')} - breakpoint='xxl' + breakpoint='lg' disabled={!record.failCaseCount} color={record.failCaseCount ? 'primary' : 'disabled'} onClick={() => { @@ -339,7 +309,7 @@ const PlanItem: FC<ReplayPlanItemProps> = (props) => { <TooltipButton icon={<FileTextOutlined />} title={t('replay.case')} - breakpoint='xxl' + breakpoint='lg' color='primary' onClick={() => { navPane({ @@ -352,7 +322,7 @@ const PlanItem: FC<ReplayPlanItemProps> = (props) => { <TooltipButton icon={<RedoOutlined />} title={t('replay.retry')} - breakpoint='xxl' + breakpoint='lg' color='primary' onClick={() => retryPlan({ planId: selectedPlan!.planId, planItemId: record.planItemId }) @@ -378,10 +348,14 @@ const PlanItem: FC<ReplayPlanItemProps> = (props) => { const { run: deleteReport } = useRequest(ReportService.deleteReport, { manual: true, ready: !!selectedPlan?.planId, - onSuccess(success) { - success - ? message.success(t('message.success', { ns: 'common' })) - : message.error(t('message.error', { ns: 'common' })); + onSuccess(success, [planId]) { + if (success) { + message.success(t('message.success', { ns: 'common' })); + onRefresh?.(); + onDelete?.(planId); + } else { + message.error(t('message.error', { ns: 'common' })); + } }, }); @@ -423,7 +397,16 @@ const PlanItem: FC<ReplayPlanItemProps> = (props) => { break; } case 'deleteReport': { - deleteReport(selectedPlan!.planId); + modal.confirm({ + maskClosable: true, + title: t('replay.confirmDeleteReport'), + icon: <ExclamationCircleFilled />, + okType: 'danger', + onOk() { + deleteReport(selectedPlan!.planId); + }, + }); + break; } } @@ -503,48 +486,34 @@ const PlanItem: FC<ReplayPlanItemProps> = (props) => { > <Row gutter={12}> <Col span={12}> - <Typography.Text type='secondary'>{t('replay.basicInfo')}</Typography.Text> - <div - css={css` - display: flex; - & > * { - flex: 1; - } - `} - > - <Statistic - title={t('replay.passRate')} - value={getPercent(selectedPlan.successCaseCount, selectedPlan.totalCaseCount)} - /> - <Statistic - title={t('replay.apiPassRate')} - value={getPercent( - selectedPlan.successOperationCount, - selectedPlan.totalOperationCount, - )} - /> - </div> + <Statistic + title={t('replay.passRate')} + value={getPercent(selectedPlan.successCaseCount, selectedPlan.totalCaseCount)} + /> <div> - {t('replay.reportId')}: {selectedPlan.planId} + <Label>{t('replay.reportId')}</Label> + {selectedPlan.planId} </div> <div> - {t('replay.reportName')}: {selectedPlan.planName} + <Label>{t('replay.reportName')}</Label> + {selectedPlan.planName} </div> <div> - {t('replay.caseRange')}:{' '} + <Label>{t('replay.caseRange')}</Label> {dayjs(new Date(selectedPlan.caseStartTime || '')).format('YYYY/MM/DD')} -{' '} {dayjs(new Date(selectedPlan.caseEndTime || '')).format('YYYY/MM/DD')} </div> <div> - {t('replay.targetHost')}: {selectedPlan.targetEnv} + <Label>{t('replay.targetHost')}</Label> + {selectedPlan.targetEnv} </div> <div> - {t('replay.executor')}: {selectedPlan.creator} + <Label>{t('replay.executor')}</Label> + {selectedPlan.creator} </div> <div> - <span> - {t('replay.recordVersion')}: {selectedPlan.caseRecordVersion || '-'} - </span> + <Label>{t('replay.recordVersion')}</Label> + {selectedPlan.caseRecordVersion || '-'} <span> {t('replay.replayVersion')}: {selectedPlan.coreVersion || '-'} diff --git a/packages/arex/src/panes/Replay/PlanReport.tsx b/packages/arex/src/panes/Replay/PlanReport.tsx index 2a6baacb9..fc6fc9a22 100644 --- a/packages/arex/src/panes/Replay/PlanReport.tsx +++ b/packages/arex/src/panes/Replay/PlanReport.tsx @@ -1,4 +1,4 @@ -import { WarningOutlined } from '@ant-design/icons'; +import { FilterOutlined, WarningOutlined } from '@ant-design/icons'; import { FullHeightSpin, HighlightRowTable, @@ -6,15 +6,16 @@ import { useArexPaneProps, useTranslation, } from '@arextest/arex-core'; -import { usePagination, useRequest } from 'ahooks'; -import { Card, theme, Tooltip, Typography } from 'antd'; +import { useRequest } from 'ahooks'; +import { Card, Tag, theme, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; -import React, { FC, useEffect, useState } from 'react'; +import React, { FC, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; import CountUp from 'react-countup'; -import { useSearchParams } from 'react-router-dom'; import { StatusTag } from '@/components'; import { ResultsState } from '@/components/StatusTag'; +import { PanesType } from '@/constant'; +import { useNavPane } from '@/hooks'; import { ReportService } from '@/services'; import { PlanStatistics } from '@/services/ReportService'; import { useMenusPanes } from '@/store'; @@ -26,16 +27,20 @@ export type PlanReportProps = { appId?: string; refreshDep?: React.Key; recordCount?: number; - onSelectedPlanChange: (selectedPlan: PlanStatistics, current?: number, row?: number) => void; + onSelectedPlanChange: (selectedPlan?: PlanStatistics) => void; }; -const PlanReport: FC<PlanReportProps> = (props) => { +export type PlanReportRef = { + select: (index: number) => void; +}; +const PlanReport = forwardRef<PlanReportRef, PlanReportProps>((props, ref) => { const { appId, refreshDep, recordCount, onSelectedPlanChange } = props; const { activePane } = useMenusPanes(); const { token } = theme.useToken(); const { t } = useTranslation(['components']); - const [searchParams] = useSearchParams(); + const navPane = useNavPane(); + const [init, setInit] = useState(true); const { data } = useArexPaneProps<{ @@ -44,14 +49,9 @@ const PlanReport: FC<PlanReportProps> = (props) => { const columns: ColumnsType<PlanStatistics> = [ { - title: t('replay.replayReportName'), + title: t('replay.reportName'), dataIndex: 'planName', ellipsis: { showTitle: false }, - render: (text) => ( - <Tooltip title={text} placement='topLeft'> - <a>{text}</a> - </Tooltip> - ), }, { title: t('replay.state'), @@ -96,7 +96,7 @@ const PlanReport: FC<PlanReportProps> = (props) => { ), }, { - title: t('replay.blocked'), + title: t('replay.queued'), width: 80, dataIndex: 'waitCaseCount', render: (text) => ( @@ -185,15 +185,24 @@ const PlanReport: FC<PlanReportProps> = (props) => { }, [activePane, props.appId]); const [selectKey, setSelectKey] = useState<string>(); - const handleRowClick: HighlightRowTableProps<PlanStatistics>['onRowClick'] = (record, index) => { - setSelectKey(record.planId); - onSelectedPlanChange(record, pagination.current, index); + const handleRowClick: HighlightRowTableProps<PlanStatistics>['onRowClick'] = (record) => { + const selected = record.planId === selectKey; + const selectedRecord = selected ? undefined : record; + setSelectKey(selectedRecord?.planId); + onSelectedPlanChange(selectedRecord); }; + useImperativeHandle( + ref, + () => ({ + select: (index) => handleRowClick?.(planStatistics[index]), + }), + [planStatistics], + ); + return ( <FullHeightSpin spinning={init} - minHeight={240} // 为了 defaultCurrent 和 defaultRow 生效,需在初次获取到数据后再挂载子组件 mountOnFirstLoading={false} > @@ -208,32 +217,47 @@ const PlanReport: FC<PlanReportProps> = (props) => { </Typography.Text> </Card> ) : ( - <HighlightRowTable<PlanStatistics> - rowKey='planId' - size='small' - loading={loading} - columns={columns} - selectKey={selectKey} - pagination={pagination} - onChange={(pagination) => { - if (Object.keys(pagination).length) { - queryPlanStatistics({ - current: pagination.current, - pageSize: pagination.pageSize, - }).then(({ list }) => handleRowClick?.(list[0], 1)); - } - }} - onRowClick={handleRowClick} - dataSource={planStatistics} - sx={{ - '.ant-table-cell-ellipsis': { - color: token.colorPrimary, - }, - }} - /> + <> + <HighlightRowTable<PlanStatistics> + rowKey='planId' + size='small' + loading={loading} + columns={columns} + selectKey={selectKey} + pagination={pagination} + onChange={(pagination) => { + if (Object.keys(pagination).length) { + queryPlanStatistics({ + current: pagination.current, + pageSize: pagination.pageSize, + }).then(({ list }) => handleRowClick?.(list[0], 1)); + } + }} + onRowClick={handleRowClick} + dataSource={planStatistics} + /> + {data?.planId && ( + <div style={{ position: 'absolute', bottom: token.margin }}> + <FilterOutlined style={{ marginRight: '8px' }} /> + <Tag + closable + onClose={() => { + console.log('close'); + navPane({ + id: appId!, + type: PanesType.REPLAY, + data: { planId: undefined }, + }); + }} + > + {data.planId} + </Tag> + </div> + )} + </> )} </FullHeightSpin> ); -}; +}); export default PlanReport; diff --git a/packages/arex/src/panes/Replay/Replay.tsx b/packages/arex/src/panes/Replay/Replay.tsx index dc22f1851..073ff810b 100644 --- a/packages/arex/src/panes/Replay/Replay.tsx +++ b/packages/arex/src/panes/Replay/Replay.tsx @@ -19,7 +19,7 @@ import { decodePaneKey } from '@/store/useMenusPanes'; import AppOwnersConfig, { AppOwnerConfigRef } from './AppOwnersConfig'; import AppTitle from './AppTitle'; import PlanItem from './PlanItem'; -import PlanReport, { PlanReportProps } from './PlanReport'; +import PlanReport from './PlanReport'; const ReplayPage: ArexPaneFC = (props) => { const { activePane } = useMenusPanes(); @@ -62,7 +62,6 @@ const ReplayPage: ArexPaneFC = (props) => { defaultParams: [appId], onSuccess(res) { setHasOwner(!!res.owners?.length); - !res.owners?.length && appOwnerConfigRef?.current?.open(); }, }); @@ -116,6 +115,7 @@ const ReplayPage: ArexPaneFC = (props) => { readOnly={!hasOwner} filter={(record) => !!record.totalCaseCount} onRefresh={handleRefreshDep} + onDelete={() => setSelectedPlan(undefined)} /> } /> diff --git a/packages/arex/src/panes/ReplayCase/Case.tsx b/packages/arex/src/panes/ReplayCase/Case.tsx index 1de5706e2..1ea0c123d 100644 --- a/packages/arex/src/panes/ReplayCase/Case.tsx +++ b/packages/arex/src/panes/ReplayCase/Case.tsx @@ -44,7 +44,6 @@ const Case: FC<CaseProps> = (props) => { { title: t('replay.recordId'), dataIndex: 'recordId', - render: (recordId, record) => <a onClick={() => props.onClick?.(record)}>{recordId}</a>, }, { title: t('replay.replayId'), diff --git a/packages/arex/vite.config.ts b/packages/arex/vite.config.ts index fa850e3d0..2428cab8a 100644 --- a/packages/arex/vite.config.ts +++ b/packages/arex/vite.config.ts @@ -13,6 +13,15 @@ export default defineConfig(async ({ mode }) => ({ __APP_VERSION__: await import('./package.json').then((pkg) => JSON.stringify(pkg.version)), __AUTH_PORT__: port.electronPort, }, + resolve: { + alias: { + '@': path.resolve('./src'), + // '@arextest/arex-core/dist': path.resolve('../arex-core/src'), + // '@arextest/arex-core': path.resolve('../arex-core/src'), + // '@arextest/arex-common': path.resolve('../arex-common/src'), + // '@arextest/arex-request': path.resolve('../arex-request/src'), + }, + }, plugins: [ svgr(), react({ @@ -40,15 +49,6 @@ export default defineConfig(async ({ mode }) => ({ : [], ], ], - resolve: { - alias: { - '@': path.resolve('./src'), - '@arextest/arex-core/dist': path.resolve('../arex-core/src'), - '@arextest/arex-core': path.resolve('../arex-core/src'), - '@arextest/arex-common': path.resolve('../arex-common/src'), - // '@arextest/arex-request': path.resolve('../arex-request/src'), - }, - }, server: { host: '0.0.0.0', port: port.vitePort,