import { CompassLink, CompassLinkType, CreateCompassComponentLink, CreateCompassComponentLinkInput, CreateLinkInput, DeleteCompassComponentLink, DeleteCompassComponentLinkInput, Link, } from '@atlassian/forge-graphql-types'; import { GqlSegment } from './types'; import constructGqlSegmentFromArray from '../constructGqlSegmentFromArray'; import generateMutationGql from '../generateMutationGql'; // eslint-disable-next-line @typescript-eslint/no-var-requires const Url = require('url-parse'); function isLinkEqual(a: Link | CreateLinkInput, b: Link | CreateLinkInput) { const aUrl = new Url(a.url); const bUrl = new Url(b.url); // eslint-disable-next-line eqeqeq return a.type === b.type && aUrl.href === bUrl.href && a.name == b.name; } function generateLinkKey(link: Link) { const url = new Url(link.url); const name = link.name === undefined ? null : link.name; return `${link.type}${url.href}${name}`; } function duplicateLinks(links: Array, filterBy: Array) { const dups: Array = []; const existingLinkKeys: Array = []; links.forEach((link) => { const linkKey: string = generateLinkKey(link); if (filterBy.some((key) => key === linkKey)) { if (existingLinkKeys.some((key) => key === linkKey)) { dups.push({ id: link.id } as CompassLink); } else { existingLinkKeys.push(linkKey); } } }); return dups; } function transformIntoCreateLinkInput( item: Link, componentId?: string, ): CreateCompassComponentLinkInput { return { componentId, link: item, }; } function transformIntoDeleteLinkInput( item: CompassLink, componentId?: string, ): DeleteCompassComponentLinkInput { return { componentId, link: item.id, }; } export default function updateLinksSegment( componentId: string, oldLinks: Array = [], newLinks: Array = undefined, ): GqlSegment { let segmentAcc = { mutation: '', parameters: [] as Array, variables: {} as Record, }; // if newLinks is null, remove existing links if (newLinks === undefined) { return segmentAcc; } newLinks = newLinks || []; // Get diff between old and new links const linksInBoth = oldLinks.filter((existingLink) => newLinks.some((newLink) => isLinkEqual(newLink, existingLink)), ); const toBeCreated = newLinks.filter( (newLink) => !linksInBoth.some((overlapLink) => isLinkEqual(overlapLink, newLink)), ); const toBeRemoved = oldLinks .filter( (oldLink) => !linksInBoth.some((overlapLink) => isLinkEqual(overlapLink, oldLink)), ) .filter((removedLink: CompassLink) => { return removedLink.type !== CompassLinkType.Document; }); let dups = [] as Array; if (linksInBoth) { // If there are links in both dedup the ones in Compass const distinctLinksInBoth = new Set( linksInBoth.map((link) => generateLinkKey(link)), ); dups = duplicateLinks(oldLinks, Array.from(distinctLinksInBoth)); } // Generate Gql segments const createLinkGqlSegment = () => generateMutationGql(CreateCompassComponentLink, 'createComponentLink'); const deleteLinkGqlSegment = () => generateMutationGql(DeleteCompassComponentLink, 'deleteComponentLink'); if (toBeRemoved.length > 0) { segmentAcc = constructGqlSegmentFromArray( toBeRemoved, deleteLinkGqlSegment, transformIntoDeleteLinkInput, segmentAcc, componentId, ); } if (toBeCreated.length > 0) { segmentAcc = constructGqlSegmentFromArray( toBeCreated, createLinkGqlSegment, transformIntoCreateLinkInput, segmentAcc, componentId, ); } if (dups.length > 0) { segmentAcc = constructGqlSegmentFromArray( dups, deleteLinkGqlSegment, transformIntoDeleteLinkInput, segmentAcc, componentId, ); } return segmentAcc; }