diff --git a/apps/aevatar-console-web/src/locales/en-US.ts b/apps/aevatar-console-web/src/locales/en-US.ts index 16bf596ed6..5cd01aac5a 100644 --- a/apps/aevatar-console-web/src/locales/en-US.ts +++ b/apps/aevatar-console-web/src/locales/en-US.ts @@ -429,6 +429,29 @@ const enUSMessages = { 'teams.detail.runtimeStatus.retired': 'Retired', 'teams.detail.runtimeStatus.running': 'Running', 'teams.detail.runtimeStatus.waiting': 'Waiting', + 'teams.detail.activity.actions.refresh': 'Refresh', + 'teams.detail.activity.actions.refreshAria': 'Refresh recent activity', + 'teams.detail.activity.actions.retry': 'Retry', + 'teams.detail.activity.actions.runTeam': 'Run team', + 'teams.detail.activity.empty.description': + 'Run the team or a published member to create the first visible run.', + 'teams.detail.activity.empty.title': 'No recent activity yet', + 'teams.detail.activity.error.description': + 'The last stable team facts remain available on Overview. Retry this read when the run list is available.', + 'teams.detail.activity.error.title': 'Recent activity could not be loaded', + 'teams.detail.activity.filteredEmpty.description': + 'Choose another status to return to the visible runs.', + 'teams.detail.activity.filteredEmpty.title': + 'No recent runs match this filter', + 'teams.detail.activity.filters.all': 'All', + 'teams.detail.activity.filters.aria': 'Filter recent activity', + 'teams.detail.activity.filters.attention': 'Needs attention', + 'teams.detail.activity.filters.failed': 'Failed', + 'teams.detail.activity.filters.reset': 'Show all recent runs', + 'teams.detail.activity.filters.running': 'Running', + 'teams.detail.activity.subtitle': + 'Latest runs exposed by the current team entry service.', + 'teams.detail.activity.title': 'Recent activity', 'teams.detail.status.active': 'Active', 'teams.detail.status.archived': 'Archived', 'teams.detail.status.bindReady': 'Callable', @@ -445,6 +468,7 @@ const enUSMessages = { 'teams.detail.status.unknown': 'Unknown status', 'teams.detail.tabList.label': 'Team detail tabs', 'teams.detail.tabs.automations': 'Automations', + 'teams.detail.tabs.activity': 'Activity', 'teams.detail.tabs.members': 'Team members', 'teams.detail.tabs.overview': 'Overview', 'teams.detail.test.actions.retry': 'Retry', diff --git a/apps/aevatar-console-web/src/locales/zh-CN.ts b/apps/aevatar-console-web/src/locales/zh-CN.ts index e6ba6949e9..4e2068a981 100644 --- a/apps/aevatar-console-web/src/locales/zh-CN.ts +++ b/apps/aevatar-console-web/src/locales/zh-CN.ts @@ -405,6 +405,27 @@ const zhCNMessages = { 'teams.detail.runtimeStatus.retired': '已停用', 'teams.detail.runtimeStatus.running': '运行中', 'teams.detail.runtimeStatus.waiting': '等待处理', + 'teams.detail.activity.actions.refresh': '刷新', + 'teams.detail.activity.actions.refreshAria': '刷新最近活动', + 'teams.detail.activity.actions.retry': '重试', + 'teams.detail.activity.actions.runTeam': '运行团队', + 'teams.detail.activity.empty.description': + '运行团队或已发布成员,即可生成第一条可见运行。', + 'teams.detail.activity.empty.title': '还没有最近活动', + 'teams.detail.activity.error.description': + '概览中的稳定团队信息仍然可用。运行列表恢复后可重试读取。', + 'teams.detail.activity.error.title': '无法加载最近活动', + 'teams.detail.activity.filteredEmpty.description': + '选择其他状态可返回当前可见的运行。', + 'teams.detail.activity.filteredEmpty.title': '没有符合当前筛选的最近运行', + 'teams.detail.activity.filters.all': '全部', + 'teams.detail.activity.filters.aria': '筛选最近活动', + 'teams.detail.activity.filters.attention': '需要处理', + 'teams.detail.activity.filters.failed': '失败', + 'teams.detail.activity.filters.reset': '显示全部最近运行', + 'teams.detail.activity.filters.running': '运行中', + 'teams.detail.activity.subtitle': '当前团队入口服务公开的最新运行。', + 'teams.detail.activity.title': '最近活动', 'teams.detail.status.active': '已启用', 'teams.detail.status.archived': '已归档', 'teams.detail.status.bindReady': '可调用', @@ -421,6 +442,7 @@ const zhCNMessages = { 'teams.detail.status.unknown': '状态未知', 'teams.detail.tabList.label': '团队详情标签', 'teams.detail.tabs.automations': '自动化', + 'teams.detail.tabs.activity': '活动', 'teams.detail.tabs.members': '团队成员', 'teams.detail.tabs.overview': '概览', 'teams.detail.test.actions.retry': '重试', diff --git a/apps/aevatar-console-web/src/pages/teams/components/TeamRecentRunsList.tsx b/apps/aevatar-console-web/src/pages/teams/components/TeamRecentRunsList.tsx new file mode 100644 index 0000000000..2bbbc8297a --- /dev/null +++ b/apps/aevatar-console-web/src/pages/teams/components/TeamRecentRunsList.tsx @@ -0,0 +1,184 @@ +import { + ClockCircleOutlined, + EyeOutlined, + InfoCircleOutlined, +} from "@ant-design/icons"; +import { Button, Tooltip, Typography, theme } from "antd"; +import { useIntl } from "@umijs/max"; +import React from "react"; +import { AevatarInspectorEmpty } from "@/shared/ui/aevatarPageShells"; +import { DetailPill, FactLine } from "./TeamDetailPrimitives"; + +export type TeamActivityRunRow = { + readonly detailsHref?: string; + readonly detailItems: readonly { + readonly label: string; + readonly value: string; + }[]; + readonly detailTooltipLabel: string; + readonly memberLabel: string; + readonly outputPreview: string; + readonly runId: string; + readonly statusKey: string; + readonly statusLabel: string; + readonly statusStyle: React.CSSProperties; + readonly updatedLabel: string; + readonly workflowLabel: string; + readonly workflowMetaLabel: string; +}; + +type TeamRecentRunsListProps = { + readonly emptyDescription: string; + readonly emptyTitle: string; + readonly onNavigate?: (href: string) => void; + readonly runs: readonly TeamActivityRunRow[]; +}; + +const TeamRecentRunsList: React.FC = ({ + emptyDescription, + emptyTitle, + onNavigate, + runs, +}) => { + const intl = useIntl(); + const { token } = theme.useToken(); + const handleNavigate = React.useCallback( + (href?: string) => (event: React.MouseEvent) => { + if (!href || !onNavigate) { + return; + } + + event.preventDefault(); + onNavigate(href); + }, + [onNavigate], + ); + + if (runs.length === 0) { + return ( +
+ +
+ ); + } + + return ( +
+ {runs.map((run) => { + const statusAccent = + typeof run.statusStyle.color === "string" + ? run.statusStyle.color + : token.colorBorder; + + return ( +
+
+
+ + + {run.updatedLabel} + + + {run.detailItems.map((item) => ( + + {item.label}: {item.value} + + ))} +
+ } + > + + +
+ {run.memberLabel} + + {run.workflowMetaLabel} + + +
+ {run.detailsHref ? ( + + + {onOpenTeamTest ? ( + + ) : null} + + + + + aria-label={intl.formatMessage({ + defaultMessage: "Filter recent activity", + id: "teams.detail.activity.filters.aria", + })} + block + onChange={setFilter} + options={[ + { + label: renderFilterLabel(filterLabels.all, ), + value: "all", + }, + { + label: renderFilterLabel( + filterLabels.attention, + , + ), + value: "attention", + }, + { + label: renderFilterLabel(filterLabels.running, ), + value: "running", + }, + { + label: renderFilterLabel(filterLabels.failed, ), + value: "failed", + }, + ]} + value={filter} + /> + + {loading ? ( +
+ {[0, 1, 2].map((item) => ( +
+ +
+ ))} +
+ ) : error ? ( + + {intl.formatMessage({ + defaultMessage: "Retry", + id: "teams.detail.activity.actions.retry", + })} + + } + description={intl.formatMessage({ + defaultMessage: + "The last stable team facts remain available on Overview. Retry this read when the run list is available.", + id: "teams.detail.activity.error.description", + })} + title={intl.formatMessage({ + defaultMessage: "Recent activity could not be loaded", + id: "teams.detail.activity.error.title", + })} + showIcon + type="error" + /> + ) : ( + <> + + {!hasFilteredResults && runs.length > 0 ? ( +
+ +
+ ) : null} + + )} + + ); +}; + +export default TeamActivityTab; diff --git a/apps/aevatar-console-web/src/pages/teams/tabs/TeamOverviewTab.tsx b/apps/aevatar-console-web/src/pages/teams/tabs/TeamOverviewTab.tsx index 2af117279d..e643f88ce8 100644 --- a/apps/aevatar-console-web/src/pages/teams/tabs/TeamOverviewTab.tsx +++ b/apps/aevatar-console-web/src/pages/teams/tabs/TeamOverviewTab.tsx @@ -1,8 +1,5 @@ import { - ClockCircleOutlined, - EyeOutlined, HistoryOutlined, - InfoCircleOutlined, PlayCircleOutlined, ToolOutlined, } from "@ant-design/icons"; @@ -10,10 +7,10 @@ import { Button, Space, Tooltip, Typography, theme } from "antd"; import { useIntl } from "@umijs/max"; import React from "react"; import { AevatarInspectorEmpty } from "@/shared/ui/aevatarPageShells"; -import { - DetailPill, - FactLine, -} from "../components/TeamDetailPrimitives"; +import TeamRecentRunsList, { + type TeamActivityRunRow, +} from "../components/TeamRecentRunsList"; +import { DetailPill } from "../components/TeamDetailPrimitives"; type OverviewCompositionRow = { readonly canConfigure?: boolean; @@ -42,23 +39,6 @@ type OverviewConfigurationRow = { readonly value: string; }; -type OverviewRunRow = { - readonly detailsHref?: string; - readonly detailItems: readonly { - readonly label: string; - readonly value: string; - }[]; - readonly detailTooltipLabel: string; - readonly memberLabel: string; - readonly outputPreview: string; - readonly runId: string; - readonly statusLabel: string; - readonly statusStyle: React.CSSProperties; - readonly updatedLabel: string; - readonly workflowLabel: string; - readonly workflowMetaLabel: string; -}; - type TeamOverviewTabProps = { readonly configurationDetailRows: readonly OverviewConfigurationRow[]; readonly compositionRows: readonly OverviewCompositionRow[]; @@ -79,7 +59,7 @@ type TeamOverviewTabProps = { readonly entryMemberId?: string | null; readonly entryMemberLabel?: string; readonly entryMemberUpdating?: boolean; - readonly latestRuns?: readonly OverviewRunRow[]; + readonly latestRuns?: readonly TeamActivityRunRow[]; readonly latestVisibleUpdateLabel: string; readonly latestVisibleUpdateNote: string; readonly onClearEntryMember?: () => void; @@ -502,103 +482,16 @@ const TeamOverviewTab: React.FC = ({ })} - {latestRuns.length > 0 ? ( -
- {latestRuns.map((run) => ( -
-
- - - - {run.updatedLabel} - - - {run.detailItems.map((item) => ( - - {item.label}: {item.value} - - ))} -
- } - > - - - - - - {run.memberLabel} - - {run.workflowMetaLabel} - - -
- {run.detailsHref ? ( - -
- ))} - - ) : ( -
- -
- )} + ); diff --git a/apps/aevatar-console-web/src/shared/navigation/teamRoutes.test.ts b/apps/aevatar-console-web/src/shared/navigation/teamRoutes.test.ts index 96416d9132..6bf261439d 100644 --- a/apps/aevatar-console-web/src/shared/navigation/teamRoutes.test.ts +++ b/apps/aevatar-console-web/src/shared/navigation/teamRoutes.test.ts @@ -28,6 +28,32 @@ describe("teamRoutes", () => { ); }); + it("recognizes the team activity tab without collapsing runtime context", () => { + const memberId = "member-alpha"; + const workflowId = "workflow-alpha"; + const publishedServiceId = "service-alpha"; + + expect( + readTeamDetailRouteState( + `?memberId=${memberId}&workflowId=${workflowId}&serviceId=${publishedServiceId}&runId=run-alpha&tab=activity`, + "/scopes/scope-alpha/teams/team-alpha", + ), + ).toEqual({ + memberId, + routeMemberId: "", + runId: "run-alpha", + scopeId: "scope-alpha", + serviceId: publishedServiceId, + tab: "activity", + teamId: "team-alpha", + testTeam: false, + workflowId, + }); + expect(memberId).not.toBe(workflowId); + expect(memberId).not.toBe(publishedServiceId); + expect(workflowId).not.toBe(publishedServiceId); + }); + it("falls back to the scope resolver when the scope is empty", () => { expect( buildTeamDetailHref({ diff --git a/apps/aevatar-console-web/src/shared/navigation/teamRoutes.ts b/apps/aevatar-console-web/src/shared/navigation/teamRoutes.ts index 3dc335531b..fd7feca506 100644 --- a/apps/aevatar-console-web/src/shared/navigation/teamRoutes.ts +++ b/apps/aevatar-console-web/src/shared/navigation/teamRoutes.ts @@ -2,6 +2,7 @@ import { buildStudioRoute } from '@/shared/studio/navigation'; type TeamDetailTab = | 'overview' + | 'activity' | 'automations' | 'members'; @@ -38,6 +39,7 @@ function parseTeamTab( ): TeamDetailTab { switch (trimOptional(value).toLowerCase()) { case 'overview': + case 'activity': case 'automations': case 'members': return trimOptional(value).toLowerCase() as TeamDetailTab;