///
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,
};
};