import { ApiPayload, CompassExternalAliasInput, Component, ComponentPayload, CreateLinkInput, } from '@atlassian/forge-graphql-types'; import { CompassYaml } from '../../../../types'; import { CompassRequests } from '../../../../compass-requests'; import { DUPLICATED_NAME_ERROR_MESSAGE, EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, } from './constants'; import { UnableToFindComponentDuringUpdateError } from './errors'; import { generateExternalIdWithPrefix, isYamlNameValid, updateComponent, updateExternalAlias, } from './helpers'; import reportSyncError from '../report-sync-error'; import { InvalidConfigFileError } from '../validate-config-file/models/errors'; import { ConfigAsCodeRequests } from '../../../../config-as-code-requests'; import { createActionForComponentByPath } from './createActionHelper'; import { COMPONENT_NOT_FOUND } from '../../../../helpers'; type UpdateActionWithExistingComponentByNameInput = { requestApi: CompassRequests; componentByName: Component; componentYaml: CompassYaml; deduplicationId?: string; oldPath?: string; newPath: string; cloudId: string; externalSourceURL?: string; additionalLinks?: CreateLinkInput[]; additionalExternalAliases?: CompassExternalAliasInput[]; }; type UpdateOrCreateActionWithComponentPathInput = { requestApi: CompassRequests; componentYaml: CompassYaml; deduplicationId?: string; oldPath?: string; newPath: string; cloudId: string; externalSourceURL?: string; additionalLinks?: CreateLinkInput[]; additionalExternalAliases?: CompassExternalAliasInput[]; }; type UnlinkComponentIfRetargetedByNewIdInput = { requestApi: CompassRequests; configAsCodeApi: ConfigAsCodeRequests; cloudId: string; componentYaml: CompassYaml; oldPath?: string; deduplicationId?: string; additionalExternalAliases?: CompassExternalAliasInput[]; }; type UpdateNameExternalAliasInput = { yamlName?: string; requestApi: CompassRequests; componentId: string; deduplicationId: string; existingNameAliasId: string; newNameExternalAlias: CompassExternalAliasInput | null; }; const updateActionForExistingComponentByName = async ({ requestApi, componentByName, oldPath, newPath, deduplicationId, componentYaml, cloudId, externalSourceURL, additionalLinks, additionalExternalAliases, }: UpdateActionWithExistingComponentByNameInput): Promise => { const { name } = componentYaml; const currentPathAlias = componentByName.externalAliases.find( (alias) => alias.externalSource === EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, ); const oldExternalAlias = { externalId: generateExternalIdWithPrefix(deduplicationId, oldPath), externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, }; const newExternalAlias = { externalId: generateExternalIdWithPrefix(deduplicationId, newPath), externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, }; if (currentPathAlias?.externalAliasId === oldExternalAlias.externalId) { // Normal Update if (oldPath !== newPath) { await updateExternalAlias( requestApi, componentByName.id, oldExternalAlias, newExternalAlias, ); } return updateComponent( cloudId, requestApi, componentByName, componentYaml, additionalLinks, additionalExternalAliases, externalSourceURL, ); } const responseByOldPathAlias = await requestApi.getComponentByExternalAlias({ cloudId, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, externalId: oldExternalAlias.externalId, options: { includeLinks: true, includeCustomFields: true, includeCustomFieldOptions: true, }, }); if (!responseByOldPathAlias.data.component) { throw new UnableToFindComponentDuringUpdateError( responseByOldPathAlias.errors, ); } if (oldPath !== newPath) { await updateExternalAlias( requestApi, componentByName.id, oldExternalAlias, newExternalAlias, ); } const existingNameAlias = responseByOldPathAlias.data.component.externalAliases.find( (alias) => alias.externalSource === EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, ); if (name === componentByName.name) { if (existingNameAlias) { await requestApi.deleteExternalAlias({ componentId: responseByOldPathAlias.data.component.id, externalAlias: { externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, externalId: existingNameAlias.externalAliasId, }, }); } await reportSyncError( new InvalidConfigFileError([DUPLICATED_NAME_ERROR_MESSAGE]), responseByOldPathAlias.data.component.id, externalSourceURL, requestApi, ); throw new InvalidConfigFileError([DUPLICATED_NAME_ERROR_MESSAGE]); } else { return updateComponent( cloudId, requestApi, componentByName, componentYaml, additionalLinks, additionalExternalAliases, externalSourceURL, ); } }; const updateNameExternalAlias = async ({ yamlName, requestApi, componentId, deduplicationId, existingNameAliasId, newNameExternalAlias, }: UpdateNameExternalAliasInput) => { const oldNameExternalAlias = { externalId: existingNameAliasId, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, }; if (!isYamlNameValid(yamlName)) { await updateExternalAlias( requestApi, componentId, oldNameExternalAlias, null, ); } else if ( generateExternalIdWithPrefix(deduplicationId, yamlName) !== existingNameAliasId ) { await updateExternalAlias( requestApi, componentId, oldNameExternalAlias, newNameExternalAlias, ); } }; const updateOrCreateComponentByPath = async ({ requestApi, oldPath, newPath, deduplicationId, componentYaml, cloudId, externalSourceURL, additionalLinks, additionalExternalAliases, }: UpdateOrCreateActionWithComponentPathInput): Promise => { const { name: yamlName } = componentYaml; const newPathExternalAlias = { externalId: generateExternalIdWithPrefix(deduplicationId, newPath), externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, }; let nameExternalAliasWithNameFromYaml = null; if (isYamlNameValid(yamlName)) { nameExternalAliasWithNameFromYaml = { externalId: generateExternalIdWithPrefix(deduplicationId, yamlName), externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, }; } const componentByPathAliasData = await requestApi.getComponentByExternalAlias( { cloudId, externalId: generateExternalIdWithPrefix(deduplicationId, oldPath), externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, options: { includeLinks: true, includeCustomFields: true, }, }, ); const shouldThrowError = !componentByPathAliasData.data?.component && componentByPathAliasData.errors.length && componentByPathAliasData.errors.every( (error) => error.message !== COMPONENT_NOT_FOUND, ); if (shouldThrowError) { throw new UnableToFindComponentDuringUpdateError( componentByPathAliasData.errors, ); } const shouldCreateComponent = !componentByPathAliasData.data?.component && componentByPathAliasData.errors.length && componentByPathAliasData.errors.some( (error) => error.message === COMPONENT_NOT_FOUND, ); if (shouldCreateComponent) { return createActionForComponentByPath({ requestApi, newPath, deduplicationId, componentYaml, cloudId, externalSourceURL, additionalLinks, additionalExternalAliases, }); } const componentId = componentByPathAliasData.data.component.id; const existingPathAlias = componentByPathAliasData.data.component.externalAliases.find( (alias) => alias.externalSource === EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, ); const existingNameAlias = componentByPathAliasData.data.component.externalAliases.find( (alias) => alias.externalSource === EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, ); if (oldPath !== newPath) { const oldPathExternalAlias = { externalId: existingPathAlias.externalAliasId, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, }; await updateExternalAlias( requestApi, componentId, oldPathExternalAlias, newPathExternalAlias, ); } if (existingNameAlias) { await updateNameExternalAlias({ yamlName, componentId, requestApi, deduplicationId, existingNameAliasId: existingNameAlias.externalAliasId, newNameExternalAlias: nameExternalAliasWithNameFromYaml, }); } else if (nameExternalAliasWithNameFromYaml) { await requestApi.createExternalAlias({ componentId, externalAlias: nameExternalAliasWithNameFromYaml, }); } return updateComponent( cloudId, requestApi, componentByPathAliasData?.data?.component, componentYaml, additionalLinks, additionalExternalAliases, externalSourceURL, ); }; const isComponentRetargeted = ( componentByPathAliasData: ApiPayload, newId: string, ) => { const { success, data } = componentByPathAliasData; return success && data?.component && data.component.id !== newId; }; const unlinkComponentIfRetargetedByNewId = async ({ requestApi, configAsCodeApi, cloudId, componentYaml, oldPath, deduplicationId, additionalExternalAliases, }: UnlinkComponentIfRetargetedByNewIdInput): Promise => { const componentByPathAliasData = await requestApi.getComponentByExternalAlias( { cloudId, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, externalId: generateExternalIdWithPrefix(deduplicationId, oldPath), }, ); if (isComponentRetargeted(componentByPathAliasData, componentYaml.id)) { await configAsCodeApi.unlinkComponent({ cloudId, filePath: oldPath, componentId: componentByPathAliasData.data.component.id, deduplicationId, additionalExternalAliasesToRemove: additionalExternalAliases, }); } }; export { updateActionForExistingComponentByName, updateOrCreateComponentByPath, unlinkComponentIfRetargetedByNewId, };