/// import { internalMetrics } from '@forge/metrics'; import yaml from 'js-yaml'; import { ApiPayload, CompassLinkType, Component, ComponentPayload, SdkError, SyncComponentWithFileInput, } from '@atlassian/forge-graphql-types'; import { CompassYaml } from '../../types'; import { ConfigAsCodeRequests } from '../../config-as-code-requests'; import { INVALID_YAML_ERROR } from './helpers/validate-config-file/models/error-messages'; import { InvalidConfigFileError } from './helpers/validate-config-file/models/errors'; import { createOrUpdateComponent } from './helpers/createOrUpdateComponent/createOrUpdateComponent'; import { FAILURE_METRIC, INVALID_CONFIG_METRIC, SUCCESS_METRIC, UPDATE_TIME_METRIC, } from './helpers/metrics'; import { HELLO_CLOUD_ID, HELLO_RESTRICTED_TYPE, INVALID_REQUEST_ERROR_TYPE, } from '../../helpers/constants'; import { ModificationNotSupportedForThatType } from './helpers/createOrUpdateComponent/errors'; import reportSyncError from './helpers/report-sync-error'; declare module '../../config-as-code-requests' { interface ConfigAsCodeRequests { /** * Updates a component with data coming from the configuration as code file. * * **Required Oauth Scopes:** `write:component:compass` */ syncComponentWithFile( input: SyncComponentWithFileInput, ): Promise>; } } ConfigAsCodeRequests.prototype.syncComponentWithFile = async function ( input: SyncComponentWithFileInput, ) { const { configFile, externalSourceURL, cloudId, additionalLinks, additionalExternalAliases, configFileMetadata, } = input; const errorsResp: Array = []; let componentYaml: CompassYaml; try { componentYaml = yaml.load(configFile); } catch (e) { console.warn({ message: 'Error parsing yaml file', error: e }); internalMetrics.counter(INVALID_CONFIG_METRIC).incr(); return { errors: [new InvalidConfigFileError([INVALID_YAML_ERROR])], success: false, }; } let currentComponent: Component | null; const { id: componentId, typeId } = componentYaml; if (process.env.FORGE_GRAPHQL_LOGGING) { console.log({ message: 'Syncing component with file', componentId, }); } if (cloudId === HELLO_CLOUD_ID && typeId === HELLO_RESTRICTED_TYPE) { const error = `Unable to sync ${HELLO_RESTRICTED_TYPE} on this cloudId: ${HELLO_CLOUD_ID}. Please use the UI to create or update ${HELLO_RESTRICTED_TYPE} components.`; console.warn(error); try { const componentByIdResp = await this.requests.getComponent({ componentId, }); const component = componentByIdResp?.data?.component; if ( component && component?.dataManager?.externalSourceURL === externalSourceURL ) { await reportSyncError( new InvalidConfigFileError([error]), componentId, externalSourceURL, this.requests, ); } } catch (e) { console.warn('Failed to report sync error:', e); } internalMetrics.counter(INVALID_CONFIG_METRIC).incr(); return { success: false, errors: [ { message: 'Unable to sync that component type on this site', statusCode: 400, errorType: INVALID_REQUEST_ERROR_TYPE, }, ], }; } const timer = internalMetrics.timer(UPDATE_TIME_METRIC).measure(); if (componentYaml.links) { componentYaml.links = componentYaml.links.filter( (link) => link.type !== CompassLinkType.Document, ); } try { currentComponent = await createOrUpdateComponent({ requestApi: this.requests, configAsCodeApi: this, cloudId, deduplicationId: configFileMetadata.deduplicationId, oldPath: configFileMetadata.oldPath, newPath: configFileMetadata.newPath, configFileAction: configFileMetadata.configFileAction, componentYaml, additionalExternalAliases, externalSourceURL, additionalLinks, }); } catch (e) { if (e instanceof ModificationNotSupportedForThatType) { internalMetrics.counter(INVALID_CONFIG_METRIC).incr(); return { success: false, errors: [ { message: 'Unable to sync that component type on this site', statusCode: 400, errorType: INVALID_REQUEST_ERROR_TYPE, }, ], }; } console.error(e); internalMetrics.counter(FAILURE_METRIC).incr(); return { success: false, errors: [e], }; } finally { timer.stop(); } internalMetrics .counter(errorsResp.length === 0 ? SUCCESS_METRIC : FAILURE_METRIC) .incr(); return { errors: errorsResp, data: { component: currentComponent }, success: errorsResp.length === 0, }; };