From 675fcc4824676fd6d6d8b6ee967b32f7dd485e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 10 Jun 2026 15:39:33 +0200 Subject: [PATCH] Add Import action for catalog vendors in trackers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A tracker pattern resolved only to a catalog vendor now shows an "Import to third parties" action in its row menu. It calls importThirdPartyFromCommon for the pattern's common third party and, on success, links the imported org vendor onto the row so it stops showing the "Common catalog" badge. Sibling patterns of the same vendor are backfilled server-side and pick up the link on the next fetch of the list, so the store update only needs to reflect the clicked row. Signed-off-by: Émile Ré --- .../_components/TrackerPatternRow.tsx | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/apps/console/src/pages/organizations/cookie-banners/configuration/trackers/_components/TrackerPatternRow.tsx b/apps/console/src/pages/organizations/cookie-banners/configuration/trackers/_components/TrackerPatternRow.tsx index 23c509406..430117fb3 100644 --- a/apps/console/src/pages/organizations/cookie-banners/configuration/trackers/_components/TrackerPatternRow.tsx +++ b/apps/console/src/pages/organizations/cookie-banners/configuration/trackers/_components/TrackerPatternRow.tsx @@ -12,7 +12,7 @@ // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -import { EyeIcon, EyeSlashIcon } from "@phosphor-icons/react"; +import { DownloadSimpleIcon, EyeIcon, EyeSlashIcon } from "@phosphor-icons/react"; import { formatError, getTrackerSourceBadge, getTrackerTypeBadge, type GraphQLError, humanizeSeconds } from "@probo/helpers"; import { useTranslate } from "@probo/i18n"; import { @@ -32,8 +32,10 @@ import { ConnectionHandler } from "relay-runtime"; import type { TrackerPatternRowDeleteMutation } from "#/__generated__/core/TrackerPatternRowDeleteMutation.graphql"; import type { TrackerPatternRowFragment$key } from "#/__generated__/core/TrackerPatternRowFragment.graphql"; +import type { TrackerPatternRowImportMutation } from "#/__generated__/core/TrackerPatternRowImportMutation.graphql"; import type { TrackerPatternRowMoveMutation } from "#/__generated__/core/TrackerPatternRowMoveMutation.graphql"; import type { TrackerPatternRowUpdateMutation } from "#/__generated__/core/TrackerPatternRowUpdateMutation.graphql"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; import { MoveToCategorySelect } from "./MoveToCategorySelect"; import { TrackerPatternRowEdit } from "./TrackerPatternRowEdit"; @@ -130,6 +132,22 @@ const updatePatternMutation = graphql` } `; +const importThirdPartyMutation = graphql` + mutation TrackerPatternRowImportMutation( + $input: ImportThirdPartyFromCommonInput! + ) { + importThirdPartyFromCommon(input: $input) { + created + thirdPartyEdge { + node { + id + name + } + } + } + } +`; + interface TrackerPatternRowProps { patternKey: TrackerPatternRowFragment$key; connectionId: string; @@ -139,6 +157,7 @@ export function TrackerPatternRow({ patternKey, connectionId }: TrackerPatternRo const { __ } = useTranslate(); const { toast } = useToast(); const confirm = useConfirm(); + const organizationId = useOrganizationId(); const pattern = useFragment(trackerPatternFragment, patternKey); const [isEditing, setIsEditing] = useState(false); @@ -149,6 +168,8 @@ export function TrackerPatternRow({ patternKey, connectionId }: TrackerPatternRo = useMutation(movePatternMutation); const [updatePattern, isUpdating] = useMutation(updatePatternMutation); + const [importThirdParty, isImporting] + = useMutation(importThirdPartyMutation); const handleDelete = () => { confirm( @@ -249,6 +270,49 @@ export function TrackerPatternRow({ patternKey, connectionId }: TrackerPatternRo }); }; + const handleImport = () => { + const commonThirdParty = pattern.commonThirdParty; + if (!commonThirdParty || isImporting) { + return; + } + + importThirdParty({ + variables: { + input: { + organizationId, + commonThirdPartyId: commonThirdParty.id, + }, + }, + updater(store) { + const node = store + .getRootField("importThirdPartyFromCommon") + ?.getLinkedRecord("thirdPartyEdge") + ?.getLinkedRecord("node"); + if (!node) { + return; + } + + // Reflect the import on the clicked pattern immediately. Sibling + // patterns of the same vendor are backfilled server-side and pick + // up the link on the next fetch of the list. + const patternRecord = store.get(pattern.id); + if (patternRecord) { + patternRecord.setLinkedRecord(node, "thirdParty"); + } + }, + onCompleted(_, errors) { + if (errors?.length) { + toast({ title: __("Error"), description: errors[0].message, variant: "error" }); + return; + } + toast({ title: __("Success"), description: __("Third party imported"), variant: "success" }); + }, + onError(error) { + toast({ title: __("Error"), description: formatError(__("Failed to import third party"), error as GraphQLError), variant: "error" }); + }, + }); + }; + const handleSaveEdit = (data: { description: string; maxAgeSeconds: number | null }) => { updatePattern({ variables: { @@ -352,6 +416,14 @@ export function TrackerPatternRow({ patternKey, connectionId }: TrackerPatternRo + {!pattern.thirdParty && pattern.commonThirdParty && ( + + {__("Import to third parties")} + + )}