import { CompassRequests } from 'src/compass-requests'; import { ApiPayload, CompassExternalAliasInput, Component, CreateCompassComponentExternalAliasInput, CreateLinkInput, ExternalAliasPayload, UpdateComponentInput, } from '@atlassian/forge-graphql-types'; import { CompassYaml } from '../../../../types'; import { buildComponentTypeFromYaml, buildSlugRefsFromYaml, } from '../componentReferenceHelper'; import { getCustomFieldsForUpdate } from '../customFieldUpdateHelper'; import validateConfigFile from '../validate-config-file'; import { shouldCreateExternalAlias } from '../shouldCreateExternalAlias'; import { transformFieldsFromYamlConfig, transformRelationshipsFromYamlConfig, } from '../yamlConfigTransforms'; import appendLinks from '../append-links'; import { DEFAULT_UNNAMED_COMPONENT_NAME } from './constants'; import reportSyncError from '../report-sync-error'; import { MAX_NAME_LENGTH } from '../../../../helpers/constants'; import { getFormattedErrors, hasRejections, } from '../../../../helpers/promise-allsettled-helpers'; import { reportHumanReadableIds } from '../metrics'; export const isYamlNameValid = (yamlName?: unknown): boolean => { if (!yamlName) { return false; } return typeof yamlName === 'string' && yamlName.length <= MAX_NAME_LENGTH; }; export const generateExternalIdWithPrefix = ( deduplicationId: string, key: string, ): string => { const externalId = key ? key.toLowerCase() : key; return `${deduplicationId}:${externalId}`; }; export const updateExternalAlias = async ( requestApi: CompassRequests, componentId: string, oldExternalAlias: CompassExternalAliasInput, newExternalAlias: CompassExternalAliasInput | null, ): Promise => { await requestApi.deleteExternalAlias({ componentId, externalAlias: oldExternalAlias, }); if (newExternalAlias) { await requestApi.createExternalAlias({ componentId, externalAlias: newExternalAlias, }); } }; export const updateComponent = async ( cloudId: string, requestApi: CompassRequests, currentComponent: Component, componentYaml: CompassYaml, additionalLinks?: CreateLinkInput[], additionalExternalAliases?: CompassExternalAliasInput[], externalSourceURL?: string, ): Promise => { try { const { name: yamlName, fields, description, slug, ownerId, links, relationships, typeId, customFields, labels, } = componentYaml; const name = isYamlNameValid(yamlName) ? yamlName : DEFAULT_UNNAMED_COMPONENT_NAME; validateConfigFile(componentYaml, currentComponent); if (additionalExternalAliases) { const createExternalAliasPromises: Promise< ApiPayload >[] = []; additionalExternalAliases.forEach((value) => { const { externalId, externalSource } = value; if ( shouldCreateExternalAlias( externalId, externalSource, currentComponent, ) ) { createExternalAliasPromises.push( requestApi.createExternalAlias({ componentId: currentComponent.id, externalAlias: { externalId, externalSource, }, } as CreateCompassComponentExternalAliasInput), ); } }); const settledResult = await Promise.allSettled( createExternalAliasPromises, ); if (hasRejections(settledResult)) { throw new Error( `Error in creating external aliases for cloud Id ${ currentComponent.id }: ${getFormattedErrors(settledResult)}`, ); } } const slugRefs = buildSlugRefsFromYaml(cloudId, componentYaml); let dependenciesBySlugs: Record = {}; const isRelationshipsBySlugsEnabled = process.env.ENABLE_RELATIONSHIPS_BY_SLUGS === 'true'; if (isRelationshipsBySlugsEnabled && slugRefs.length > 0) { reportHumanReadableIds(slugRefs.length); const componentsResponse = await requestApi.getComponentsByReferences( slugRefs, ); if (componentsResponse.data?.components) { dependenciesBySlugs = componentsResponse.data.components.reduce( (acc, component) => { acc[component.slug] = component.id; return acc; }, {} as Record, ); } } const isCustomTypesByNameEnabled = process.env.ENABLE_CUSTOM_TYPES_BY_NAME === 'true'; const convertedTypeId = isCustomTypesByNameEnabled ? await buildComponentTypeFromYaml( cloudId, typeId, requestApi, currentComponent, ) : typeId ?? currentComponent.typeId ?? currentComponent.type; // fall back on existing logic const customFieldsForUpdateFormat = getCustomFieldsForUpdate( customFields, currentComponent.customFields, ); const updateComponentInput = { id: currentComponent.id, name, fields: transformFieldsFromYamlConfig(fields), description: description || null, slug: slug || null, ownerId: ownerId || null, typeId: convertedTypeId, customFields: customFieldsForUpdateFormat, links: appendLinks(currentComponent, links, additionalLinks), relationships: transformRelationshipsFromYamlConfig( relationships, dependenciesBySlugs, ), labels: labels || null, dataManager: { externalSourceURL, }, currentComponent, } as UpdateComponentInput; if (labels === undefined) { delete updateComponentInput.labels; } const updateResponse = await requestApi.updateComponent( updateComponentInput, ); if (updateResponse.errors.length) { const errorMessages = updateResponse.errors .map((error) => error.message) .join(', '); console.error(`Error while updating component: ${errorMessages}`); } return updateResponse.data?.component; } catch (error) { console.warn( 'createOrUpdateComponent failed while updating the component', error, ); await reportSyncError( error, currentComponent.id, externalSourceURL, requestApi, ); throw error; } };