import { CompassBooleanField, CompassCustomFieldInput, CompassEnumField, CreateCompassFieldInput, CustomFieldInput, } from '@atlassian/forge-graphql-types'; import { TIER_MISSING_VALUE } from './constants'; import { capitalize } from './capitalize'; const fieldKeyToCompassDefinitionId = { tier: 'compass:tier', lifecycle: 'compass:lifecycle', isMonorepoProject: 'compass:isMonorepoProject', }; const upgradeSDKFieldSupportMessage = "Verify whether you're using the most recent version of the SDK as newer versions may support recently added fields."; type FieldKey = keyof typeof fieldKeyToCompassDefinitionId; function isFieldKey(value: string): value is FieldKey { return value in fieldKeyToCompassDefinitionId; } function mapFieldKeyToCompassDefinitionId(fieldKey: string) { if (!isFieldKey(fieldKey)) { console.log( `Unrecognized field key: ${fieldKey}. ${upgradeSDKFieldSupportMessage}`, ); return undefined; } return (fieldKeyToCompassDefinitionId as any)[fieldKey]; } export function transformFieldsToGql( fields: Record | boolean> = {}, ): Array { const gqlFields: CreateCompassFieldInput[] = []; for (const [key, value] of Object.entries(fields)) { const definitionId = mapFieldKeyToCompassDefinitionId(key); if (definitionId) { if (definitionId === fieldKeyToCompassDefinitionId.tier) { // Only one tier is allowed. Grab the first entry of the enum const tierValue = Array.isArray(value) ? value : []; if (!tierValue.length) { throw Error(TIER_MISSING_VALUE); } gqlFields.push({ definition: definitionId, value: { enum: { value: [tierValue[0]], }, }, }); } else if (definitionId === fieldKeyToCompassDefinitionId.lifecycle) { const lifecycleValue = Array.isArray(value) ? value : []; gqlFields.push({ definition: definitionId, value: { enum: { value: [capitalize(lifecycleValue[0])], }, }, }); } else if ( definitionId === fieldKeyToCompassDefinitionId.isMonorepoProject ) { const boolValue = Array.isArray(value) ? value[0] : value; const translatedValue = typeof boolValue === 'boolean' ? boolValue : boolValue.toString() === 'true'; gqlFields.push({ definition: definitionId, value: { boolean: { booleanValue: translatedValue, }, }, }); } else { // Handle other enum fields const enumValue = Array.isArray(value) ? value : [String(value)]; gqlFields.push({ definition: definitionId, value: { enum: { value: enumValue, }, }, }); } } } return gqlFields; } // This needs to be revisited if more field types are added. Right now there are only the // CompassEnumField and CompassBooleanField types, implementation of CompassField. Other types // may eventually be added so we are going to try to do this conversion and if it does not // work then we will skip transforming that field and prompt the user to upgrade as the // latest SDK version might have added support for this mapping export function transformEnumGqlFields( fields: Array = [], ): Record { if (!fields) { return {}; } const obj = {} as Record; for (const field of fields) { try { if (!field?.definition?.id) { console.log(`Skipping field - missing definition Id.`); } else if ('value' in field) { obj[field.definition.id] = field.value; } else if ('booleanValue' in field) { obj[field.definition.id] = field.booleanValue; } else { console.log( `Unhandled field type. Skipping field with definition ${field.definition.id}. ` + `${upgradeSDKFieldSupportMessage}`, ); } } catch (error) { console.log( `Error processing field with definition ${field?.definition?.id}: ${error}. ${upgradeSDKFieldSupportMessage}`, ); } } return obj; } export const transformCustomFieldsToGql = ( fields: CustomFieldInput[], ): CompassCustomFieldInput[] => fields.map((f) => { let field; switch (f.type) { case 'boolean': field = { booleanField: { booleanValue: f.value, definitionId: f.definitionId }, }; break; case 'number': field = { numberField: { numberValue: f.value, definitionId: f.definitionId }, }; break; case 'text': field = { textField: { textValue: f.value, definitionId: f.definitionId }, }; break; case 'user': field = { userField: { userIdValue: f.value, definitionId: f.definitionId }, }; break; case 'single_select': field = { singleSelectField: { option: f.value, definitionId: f.definitionId }, }; break; case 'multi_select': field = { multiSelectField: { options: f.value, definitionId: f.definitionId }, }; break; default: throw new Error( `Received invalid type ${ (f as CustomFieldInput).type } for custom field. Expected one of boolean, number, text, single_select, multi_select.`, ); } return field; });