diff --git a/packages/arex/src/components/InterfaceSelect.tsx b/packages/arex/src/components/InterfaceSelect.tsx index 6654c26b4..9027296ed 100644 --- a/packages/arex/src/components/InterfaceSelect.tsx +++ b/packages/arex/src/components/InterfaceSelect.tsx @@ -24,7 +24,7 @@ const InterfaceSelect = (props: SelectProps & { appId: string; open?: boolean }) () => Object.entries( data.reduce>((options, item) => { - item.operationTypes.forEach((operation) => { + item.operationTypes?.forEach((operation) => { if (options[operation]) { options[operation].push({ label: item.operationName, diff --git a/packages/arex/src/components/TagSelect.tsx b/packages/arex/src/components/TagSelect.tsx new file mode 100644 index 000000000..57f8a8de1 --- /dev/null +++ b/packages/arex/src/components/TagSelect.tsx @@ -0,0 +1,95 @@ +import { PlusOutlined } from '@ant-design/icons'; +import { css, useTranslation } from '@arextest/arex-core'; +import { useAutoAnimate } from '@formkit/auto-animate/react'; +import { Button, Cascader, Space, Tag } from 'antd'; +import React, { FC, useMemo, useState } from 'react'; + +import { CaseTags } from '@/services/ScheduleService'; + +export interface TagSelectProps { + tags?: Record; + value?: CaseTags; + onChange?: (value: CaseTags) => void; +} + +interface Option { + value: string | number; + label: string; + children?: Option[]; +} +const TagSelect: FC = (props) => { + const { tags, value, onChange } = props; + const { t } = useTranslation('common'); + + const [wrapperRef] = useAutoAnimate(); + const [addTagModalVisible, setAddTagModalVisible] = useState(false); + + const options = useMemo( + () => + Object.entries(tags || {}).map(([key, value]) => ({ + label: key, + value: key, + children: value.map((v) => ({ label: v, value: v })), + })), + [tags], + ); + + return ( + + {Object.entries(value || {}).map(([tagKey, tagValue], index) => ( + { + const newValue = { ...value }; + delete newValue[tagKey]; + onChange?.(newValue); + }} + >{`${tagKey}:${tagValue}`} + ))} + + {addTagModalVisible ? ( +
+ triggerNode.parentElement} + onBlur={() => { + setAddTagModalVisible(false); + }} + onChange={(value) => { + setAddTagModalVisible(false); + onChange?.({ ...props.value, [value[0]]: value[1] as string }); + }} + /> +
+ ) : ( + + )} +
+ ); +}; + +export default TagSelect; diff --git a/packages/arex/src/components/index.ts b/packages/arex/src/components/index.ts index 2e9350f5c..139ffbbb8 100644 --- a/packages/arex/src/components/index.ts +++ b/packages/arex/src/components/index.ts @@ -11,5 +11,6 @@ export { default as MenuSelect } from './MenuSelect'; export { default as NextInterfaceButton } from './PlanItemNavigation'; export { default as SingleCollapse } from './SingleCollapse'; export { default as StatusTag } from './StatusTag'; +export { default as TagSelect } from './TagSelect'; export { default as UserMenu } from './UserMenu'; export { default as WorkspacesMenu } from './WorkspacesMenu'; diff --git a/packages/arex/src/i18n/locales/cn/components.json b/packages/arex/src/i18n/locales/cn/components.json index 99aa77ff2..94cab02f6 100644 --- a/packages/arex/src/i18n/locales/cn/components.json +++ b/packages/arex/src/i18n/locales/cn/components.json @@ -88,6 +88,7 @@ "paths": "回放路径", "pathsTooltip": "选择需要回放的路径。如果不指定,默认情况下,所有路径下的用例都将被回放。", "pathsPlaceholder": "可选,默认选择所有路径", + "caseTags": "标签", "state": "状态", "all": "全部", "passed": "成功", diff --git a/packages/arex/src/i18n/locales/en/components.json b/packages/arex/src/i18n/locales/en/components.json index add288a6c..826bd7d9d 100644 --- a/packages/arex/src/i18n/locales/en/components.json +++ b/packages/arex/src/i18n/locales/en/components.json @@ -89,6 +89,7 @@ "pathsTooltip": "Select the paths for which cases will be replayed. If not specified, all paths will be replayed by default.", "pathsPlaceholder": "Optional, all paths are selected by default", "replayReportName": " Report Name", + "caseTags": "Tags", "state": "State", "all": "All", "passed": "Passed", diff --git a/packages/arex/src/panes/AppSetting/CompareConfig/index.tsx b/packages/arex/src/panes/AppSetting/CompareConfig/index.tsx index 7f78e7204..1c81d6477 100644 --- a/packages/arex/src/panes/AppSetting/CompareConfig/index.tsx +++ b/packages/arex/src/panes/AppSetting/CompareConfig/index.tsx @@ -146,7 +146,7 @@ const CompareConfig: FC = (props) => { Object.entries( operationList.reduce>( (options, item) => { - item.operationTypes.forEach((operation) => { + item.operationTypes?.forEach((operation) => { if (options[operation]) { options[operation].push({ label: item.operationName, diff --git a/packages/arex/src/panes/AppSetting/Other/AppBasicSetup.tsx b/packages/arex/src/panes/AppSetting/Other/AppBasicSetup.tsx index 546a64248..7b1bd03cf 100644 --- a/packages/arex/src/panes/AppSetting/Other/AppBasicSetup.tsx +++ b/packages/arex/src/panes/AppSetting/Other/AppBasicSetup.tsx @@ -39,7 +39,6 @@ const AppBasicSetup: FC = (props) => { ready: !!appId, defaultParams: [appId as string], onSuccess(res) { - console.log(res); form.setFieldsValue({ appName: res.appName, owners: res.owners ?? undefined, // set null to undefined diff --git a/packages/arex/src/panes/AppSetting/Record/FormItem/RunningStatus.tsx b/packages/arex/src/panes/AppSetting/Record/FormItem/RunningStatus.tsx index f77cbf4b4..6eb6471ff 100644 --- a/packages/arex/src/panes/AppSetting/Record/FormItem/RunningStatus.tsx +++ b/packages/arex/src/panes/AppSetting/Record/FormItem/RunningStatus.tsx @@ -1,7 +1,7 @@ import { DeleteOutlined } from '@ant-design/icons'; import { HelpTooltip, useTranslation } from '@arextest/arex-core'; import { useRequest } from 'ahooks'; -import { Badge, Button, Popconfirm, Table, Typography } from 'antd'; +import { Badge, Button, Popconfirm, Table, Tag, Typography } from 'antd'; import { ColumnsType } from 'antd/es/table'; import dayjs from 'dayjs'; import React, { FC } from 'react'; @@ -88,6 +88,19 @@ const RunningStatus: FC = (props) => { ), }, + { + title: t('replay.caseTags'), + dataIndex: 'tags', + align: 'center', + render: (_, record) => + _ + ? Object.entries(record.tags || {}).map(([key, value], index) => ( + + {key}:{value} + + )) + : '-', + }, { title: t('replay.action', { ns: 'components' }), align: 'center', diff --git a/packages/arex/src/panes/Replay/AppTitle.tsx b/packages/arex/src/panes/Replay/AppTitle.tsx index ab34e2446..af5ca31f8 100644 --- a/packages/arex/src/panes/Replay/AppTitle.tsx +++ b/packages/arex/src/panes/Replay/AppTitle.tsx @@ -25,37 +25,33 @@ import { AutoComplete, Badge, Button, - Checkbox, Collapse, DatePicker, - Divider, Form, Input, - Menu, Modal, Popover, - Select, Skeleton, - Space, theme, Typography, } from 'antd'; import dayjs, { Dayjs } from 'dayjs'; import React, { createElement, FC, ReactNode, useCallback, useMemo, useRef, useState } from 'react'; -import { InterfaceSelect } from '@/components'; +import { InterfaceSelect, TagSelect } from '@/components'; import { EMAIL_KEY, PanesType, TARGET_HOST_AUTOCOMPLETE_KEY } from '@/constant'; import { useNavPane } from '@/hooks'; import CompareNoise from '@/panes/Replay/CompareNoise'; import RecordedCaseList, { RecordedCaseListRef } from '@/panes/Replay/RecordedCaseList'; -import { ApplicationService, ScheduleService } from '@/services'; -import { MessageMap } from '@/services/ScheduleService'; +import { ScheduleService } from '@/services'; +import { CaseTags, MessageMap } from '@/services/ScheduleService'; type AppTitleProps = { appId: string; appName?: string; readOnly?: boolean; recordCount?: number; + tags?: Record; onRefresh?: () => void; onQueryRecordCount?: () => void; }; @@ -65,6 +61,7 @@ type CreatePlanForm = { targetEnv: string; caseSourceRange: [Dayjs, Dayjs]; operationList?: string[]; + caseTags?: CaseTags; }; const TitleWrapper = styled( @@ -161,6 +158,7 @@ const AppTitle: FC = ({ appName, readOnly, recordCount = 0, + tags, onRefresh, onQueryRecordCount, }) => { @@ -176,7 +174,7 @@ const AppTitle: FC = ({ const [form] = Form.useForm(); const targetEnv = Form.useWatch('targetEnv', form); - const [open, setOpen] = useState(false); + const [openPathDropdown, setOpenPathDropdown] = useState(false); const [targetHostSource, setTargetHostSource] = useLocalStorageState<{ [appId: string]: string[]; @@ -220,7 +218,7 @@ const AppTitle: FC = ({ }); }, onFinally() { - setOpen(false); + setOpenPathDropdown(false); form.resetFields(); }, }); @@ -242,6 +240,7 @@ const AppTitle: FC = ({ })), operator: email as string, replayPlanType: Number(Boolean(values.operationList?.length)), + caseTags: values.caseTags, }); // update targetHostSource @@ -294,7 +293,7 @@ const AppTitle: FC = ({ ), value: item, })) || [], - [appId, targetHostSource, open], + [appId, targetHostSource, openPathDropdown], ); const handleClickTitle = useCallback(() => caseListRef.current?.open(), [caseListRef]); @@ -312,7 +311,7 @@ const AppTitle: FC = ({ }, [appId]); const handleCloseModal = useCallback(() => { - setOpen(false); + setOpenPathDropdown(false); form.resetFields(); }, [form]); @@ -340,7 +339,7 @@ const AppTitle: FC = ({ type='primary' disabled={readOnly} icon={} - onClick={() => setOpen(true)} + onClick={() => setOpenPathDropdown(true)} > {t('replay.startButton')} @@ -349,7 +348,7 @@ const AppTitle: FC = ({ = ({ > + + + + {webhook} diff --git a/packages/arex/src/panes/Replay/PlanItem.tsx b/packages/arex/src/panes/Replay/PlanItem.tsx index d0f82cfa2..cd0698dba 100644 --- a/packages/arex/src/panes/Replay/PlanItem.tsx +++ b/packages/arex/src/panes/Replay/PlanItem.tsx @@ -32,6 +32,7 @@ import { Row, Space, Statistic, + Tag, theme, Tooltip, Typography, @@ -511,14 +512,18 @@ const PlanItem: FC = (props) => { {selectedPlan.creator} -
- - {selectedPlan.caseRecordVersion || '-'} -   - - {t('replay.replayVersion')}: {selectedPlan.coreVersion || '-'} - -
+ {selectedPlan.caseTags && ( +
+ + + {Object.entries(selectedPlan.caseTags || {}).map(([key, value]) => ( + + {key}:{value} + + ))} + +
+ )} diff --git a/packages/arex/src/panes/Replay/Replay.tsx b/packages/arex/src/panes/Replay/Replay.tsx index 073ff810b..b4ec738b3 100644 --- a/packages/arex/src/panes/Replay/Replay.tsx +++ b/packages/arex/src/panes/Replay/Replay.tsx @@ -94,6 +94,7 @@ const ReplayPage: ArexPaneFC = (props) => { appName={appInfo?.appName} readOnly={!hasOwner} recordCount={recordCount} + tags={appInfo?.tags} onRefresh={handleRefreshDep} onQueryRecordCount={queryRecordCount} /> diff --git a/packages/arex/src/panes/ReplayCase/ReplayCase.tsx b/packages/arex/src/panes/ReplayCase/ReplayCase.tsx index a9431379e..ad9771282 100644 --- a/packages/arex/src/panes/ReplayCase/ReplayCase.tsx +++ b/packages/arex/src/panes/ReplayCase/ReplayCase.tsx @@ -6,9 +6,6 @@ import { CollapseTable, DiffMatch, getJsonValueByPath, - getLocalStorage, - i18n, - I18nextLng, jsonIndexPathFilter, Label, PaneDrawer, @@ -26,12 +23,11 @@ import dayjs from 'dayjs'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { NextInterfaceButton } from '@/components'; -import { APP_ID_KEY, EMAIL_KEY, PanesType } from '@/constant'; +import { APP_ID_KEY, PanesType } from '@/constant'; import CompareConfig from '@/panes/AppSetting/CompareConfig'; import { ComparisonService, ReportService, ScheduleService } from '@/services'; import { DependencyParams, ExpirationType } from '@/services/ComparisonService'; import { InfoItem, PlanItemStatistic, ReplayCaseType } from '@/services/ReportService'; -import { MessageMap } from '@/services/ScheduleService'; import { useMenusPanes } from '@/store'; import { decodePaneKey } from '@/store/useMenusPanes'; @@ -39,9 +35,8 @@ import Case, { CaseProps } from './Case'; import SaveCase, { SaveCaseRef } from './SaveCase'; const ReplayCasePage: ArexPaneFC<{ filter?: number } | undefined> = (props) => { - const { message, notification } = App.useApp(); + const { message } = App.useApp(); const { activePane } = useMenusPanes(); - const email = getLocalStorage(EMAIL_KEY); const { t } = useTranslation(['components']); const [wrapperRef] = useAutoAnimate(); diff --git a/packages/arex/src/services/ApplicationService/getAppList.ts b/packages/arex/src/services/ApplicationService/getAppList.ts index 190052a97..bfde51b98 100644 --- a/packages/arex/src/services/ApplicationService/getAppList.ts +++ b/packages/arex/src/services/ApplicationService/getAppList.ts @@ -17,8 +17,10 @@ export type ApplicationDataType = { owner: string; owners?: string[]; organizationName: string; + organizationId: string; recordedCaseCount: number; visibilityLevel: AppVisibilityLevel; + tags: Record; }; export type RegressionListRes = Array<{ diff --git a/packages/arex/src/services/ApplicationService/getInterfacesList.ts b/packages/arex/src/services/ApplicationService/getInterfacesList.ts index a0edafe59..1d218b312 100644 --- a/packages/arex/src/services/ApplicationService/getInterfacesList.ts +++ b/packages/arex/src/services/ApplicationService/getInterfacesList.ts @@ -18,7 +18,7 @@ export type OperationInterface = { dependencyList?: string[] | null; serviceId?: string; operationName: string; - operationTypes: string[]; + operationTypes: string[] | null; operationResponse?: string | null; recordedCaseCount?: number | null; }; diff --git a/packages/arex/src/services/ConfigService/getAgentList.ts b/packages/arex/src/services/ConfigService/getAgentList.ts index a7cc98131..b47247e62 100644 --- a/packages/arex/src/services/ConfigService/getAgentList.ts +++ b/packages/arex/src/services/ConfigService/getAgentList.ts @@ -1,3 +1,4 @@ +import { CaseTags } from '@/services/ScheduleService'; import { request } from '@/utils'; export interface AgentData { @@ -8,6 +9,8 @@ export interface AgentData { recordVersion: string | null; host: string; dataUpdateTime: number; + systemProperties?: CaseTags; + tags?: CaseTags; } export async function getAgentList(appId: string) { diff --git a/packages/arex/src/services/ReportService/queryPlanStatistics.ts b/packages/arex/src/services/ReportService/queryPlanStatistics.ts index 7d0c8ce1a..9cb18c217 100644 --- a/packages/arex/src/services/ReportService/queryPlanStatistics.ts +++ b/packages/arex/src/services/ReportService/queryPlanStatistics.ts @@ -1,3 +1,4 @@ +import { CaseTags } from '@/services/ScheduleService'; import { request } from '@/utils'; export type QueryPlanStatisticsReq = { @@ -44,7 +45,9 @@ export type PlanStatistics = { waitOperationCount: number | null; totalServiceCount: number | null; percent?: number; + caseTags?: CaseTags; }; + export type QueryPlanStatisticsRes = { totalCount: number; planStatisticList: PlanStatistics[]; diff --git a/packages/arex/src/services/ScheduleService/createPlan.ts b/packages/arex/src/services/ScheduleService/createPlan.ts index 040c06247..be6cff39c 100644 --- a/packages/arex/src/services/ScheduleService/createPlan.ts +++ b/packages/arex/src/services/ScheduleService/createPlan.ts @@ -32,6 +32,8 @@ export const MessageMap: { }, }; +export type CaseTags = Record | null; + export type CreatePlanReq = { appId: string; sourceEnv: string | null; @@ -43,6 +45,7 @@ export type CreatePlanReq = { caseSourceFrom: number; caseSourceTo: number; operationCaseInfoList?: { operationId: string; replayIdList?: string[] }[]; + caseTags?: CaseTags; }; export type CreatePlanRes = {