diff --git a/docs_prod/cross-project-task-routing/phase-roadmap.md b/docs_prod/cross-project-task-routing/phase-roadmap.md index f78b8e3..f882539 100644 --- a/docs_prod/cross-project-task-routing/phase-roadmap.md +++ b/docs_prod/cross-project-task-routing/phase-roadmap.md @@ -102,6 +102,10 @@ - полноценная зеркальная activity/history - уведомления +Дополнительно реализовано: +- source-side detail показывает блок маршрутизации +- в карточке видны источник, цель, отправитель, дата отправки и связанная целевая задача + ## Этап 3. Source-side детальный экран и зеркалирование изменений ### Цель @@ -130,6 +134,20 @@ - если в target issue поменяли статус, это видно в source-side карточке - если в target issue написали комментарий, это видно в source-side карточке +### Статус + +Реализовано частично. + +Что уже работает: +- source-side detail использует отдельный экран `Внешних контуров` +- в карточке отображается блок маршрутизации с ключевой source-target связью +- текущий статус берется из фактического state целевой задачи + +Что остается: +- зеркалирование комментариев +- зеркалирование файлов +- зеркалирование activity stream и обновлений описания + ## Этап 4. Уведомления ### Цель diff --git a/plane-src/apps/api/plane/api/serializers/external_contours.py b/plane-src/apps/api/plane/api/serializers/external_contours.py index 7bb131b..9550cdf 100644 --- a/plane-src/apps/api/plane/api/serializers/external_contours.py +++ b/plane-src/apps/api/plane/api/serializers/external_contours.py @@ -78,6 +78,12 @@ class ExternalContourIssueSerializer(BaseSerializer): class ExternalContourRequestSerializer(BaseSerializer): issue = ExternalContourIssueSerializer(read_only=True) source_project_id = serializers.SerializerMethodField() + source_project_name = serializers.SerializerMethodField() + target_project_id = serializers.SerializerMethodField() + target_project_name = serializers.SerializerMethodField() + requested_by_id = serializers.SerializerMethodField() + requested_by_name = serializers.SerializerMethodField() + requested_at = serializers.SerializerMethodField() status = serializers.SerializerMethodField() class Meta: @@ -89,6 +95,12 @@ class ExternalContourRequestSerializer(BaseSerializer): "created_by", "issue", "source_project_id", + "source_project_name", + "target_project_id", + "target_project_name", + "requested_by_id", + "requested_by_name", + "requested_at", "status", ] read_only_fields = fields @@ -96,6 +108,44 @@ class ExternalContourRequestSerializer(BaseSerializer): def get_source_project_id(self, obj): return obj.extra.get("source_project_id") + def get_source_project_name(self, obj): + return obj.extra.get("source_project_name") + + def get_target_project_id(self, obj): + target_project_id = obj.extra.get("target_project_id") + if target_project_id: + return target_project_id + if obj.issue and obj.issue.project_id: + return str(obj.issue.project_id) + return None + + def get_target_project_name(self, obj): + target_project_name = obj.extra.get("target_project_name") + if target_project_name: + return target_project_name + if obj.issue and obj.issue.project: + return obj.issue.project.name + return None + + def get_requested_by_id(self, obj): + requested_by_id = obj.extra.get("requested_by_id") + if requested_by_id: + return requested_by_id + if obj.created_by_id: + return str(obj.created_by_id) + return None + + def get_requested_by_name(self, obj): + return obj.extra.get("requested_by_name") + + def get_requested_at(self, obj): + requested_at = obj.extra.get("requested_at") + if requested_at: + return requested_at + if obj.created_at: + return obj.created_at.isoformat() + return None + def get_status(self, obj): issue = obj.issue if issue and issue.state and issue.state.group in ["completed", "cancelled"]: diff --git a/plane-src/apps/web/ce/components/projects/external-contours/issue-root.tsx b/plane-src/apps/web/ce/components/projects/external-contours/issue-root.tsx index aa2137c..f30a7db 100644 --- a/plane-src/apps/web/ce/components/projects/external-contours/issue-root.tsx +++ b/plane-src/apps/web/ce/components/projects/external-contours/issue-root.tsx @@ -29,6 +29,7 @@ import { useDebouncedDuplicateIssues } from "@/plane-web/hooks/use-debounced-dup import { IssueService } from "@/services/issue/issue.service"; import { WorkItemVersionService } from "@/services/issue/work_item_version.service"; import { ExternalContoursIssueContentProperties } from "./issue-properties"; +import { ExternalContoursRequestTraceability } from "./request-traceability"; const workItemVersionService = new WorkItemVersionService(); const issueService = new IssueService(); @@ -174,6 +175,10 @@ export const ExternalContoursIssueMainContent = observer(function ExternalContou +
+ +
+
diff --git a/plane-src/apps/web/ce/components/projects/external-contours/request-traceability.tsx b/plane-src/apps/web/ce/components/projects/external-contours/request-traceability.tsx new file mode 100644 index 0000000..a74ca6e --- /dev/null +++ b/plane-src/apps/web/ce/components/projects/external-contours/request-traceability.tsx @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2023-present Plane Software, Inc. and contributors + * SPDX-License-Identifier: AGPL-3.0-only + * See the LICENSE file for details. + */ + +import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; +import { Badge } from "@plane/propel/badge"; +import type { TExternalContourRequest } from "@plane/types"; +import { Avatar } from "@plane/ui"; +import { renderFormattedDate } from "@plane/utils"; +import { ExternalContourStatePill } from "./state-pill"; + +type Props = { + contourRequest: TExternalContourRequest; +}; + +export const ExternalContoursRequestTraceability = observer(function ExternalContoursRequestTraceability(props: Props) { + const { contourRequest } = props; + const { t } = useTranslation(); + + const issue = contourRequest.issue; + const requestedByName = contourRequest.requested_by_name || issue.created_by_detail?.display_name || t("common.none"); + const requestedAt = contourRequest.requested_at || contourRequest.created_at; + const targetProjectName = contourRequest.target_project_name || issue.project_detail?.name || t("common.none"); + const targetIssueKey = + issue.project_detail?.identifier && issue.sequence_id + ? `${issue.project_detail.identifier}-${issue.sequence_id}` + : issue.sequence_id + ? `#${issue.sequence_id}` + : t("common.none"); + + return ( +
+
+
{t("external_contours_page.traceability.title")}
+

{t("external_contours_page.traceability.description")}

+
+ +
+
+
{t("external_contours_page.traceability.source_contour")}
+
+ {contourRequest.source_project_name || t("common.none")} +
+
+ +
+
{t("external_contours_page.traceability.target_contour")}
+
+ {targetProjectName} +
+
+ +
+
{t("external_contours_page.traceability.status")}
+
+ +
+
+ +
+
{t("external_contours_page.traceability.requested_by")}
+
+ + {requestedByName} +
+
+ +
+
{t("external_contours_page.traceability.requested_at")}
+
+ {requestedAt ? renderFormattedDate(requestedAt) : t("common.none")} +
+
+ +
+
{t("external_contours_page.traceability.linked_item")}
+
+ {targetIssueKey} +
+
+
+
+ ); +}); diff --git a/plane-src/packages/i18n/src/locales/en/translations.ts b/plane-src/packages/i18n/src/locales/en/translations.ts index c62c873..1db265e 100644 --- a/plane-src/packages/i18n/src/locales/en/translations.ts +++ b/plane-src/packages/i18n/src/locales/en/translations.ts @@ -331,6 +331,17 @@ export default { add_due_date: "Add due date", duplicate_of: "Duplicate of", }, + traceability: { + title: "Routing", + description: + "This block shows which contour sent the request, where it was routed, and which linked work item now carries the execution.", + source_contour: "Source internal contour", + target_contour: "Target external contour", + status: "Current status", + requested_by: "Requested by", + requested_at: "Sent at", + linked_item: "Linked work item", + }, actions: { send: "Send", accept: "Accept", diff --git a/plane-src/packages/i18n/src/locales/ru/translations.ts b/plane-src/packages/i18n/src/locales/ru/translations.ts index e92fa65..5f84f16 100644 --- a/plane-src/packages/i18n/src/locales/ru/translations.ts +++ b/plane-src/packages/i18n/src/locales/ru/translations.ts @@ -488,6 +488,16 @@ export default { add_due_date: "Добавить срок выполнения", duplicate_of: "Дубликат", }, + traceability: { + title: "Маршрутизация", + description: "Здесь отображается, из какого контура ушёл запрос, куда он направлен и с каким связанным элементом он сейчас работает.", + source_contour: "Исходный внутренний контур", + target_contour: "Целевой внешний контур", + status: "Текущий статус", + requested_by: "Отправитель", + requested_at: "Отправлено", + linked_item: "Связанная задача", + }, actions: { send: "Отправить", accept: "Принять", diff --git a/plane-src/packages/types/src/external-contours.ts b/plane-src/packages/types/src/external-contours.ts index 0f8dbc4..ed6a1fc 100644 --- a/plane-src/packages/types/src/external-contours.ts +++ b/plane-src/packages/types/src/external-contours.ts @@ -24,6 +24,12 @@ export type TExternalContourRequest = { id: string; issue: TExternalContourIssue; source_project_id: string; + source_project_name?: string | null; + target_project_id?: string | null; + target_project_name?: string | null; + requested_by_id?: string | null; + requested_by_name?: string | null; + requested_at?: string | null; status: "open" | "closed"; updated_at: string; };