Skip to content

Commit

Permalink
SALTO-5929: (Salesforce) LightningPage FieldItem references #6694
Browse files Browse the repository at this point in the history
  • Loading branch information
tamtamirr authored Oct 31, 2024
1 parent b887e8e commit d6f157e
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const optionalFeaturesDefaultValues: OptionalFeaturesDefaultValues = {
removeReferenceFromFilterItemToRecordType: false,
storeProfilesAndPermissionSetsBrokenPaths: true,
picklistsAsMaps: false,
lightningPageFieldItemReference: false,
}

type BuildFetchProfileParams = {
Expand Down
4 changes: 2 additions & 2 deletions packages/salesforce-adapter/src/filters/field_references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import {
generateReferenceResolverFinder,
ReferenceContextStrategyName,
FieldReferenceDefinition,
fieldNameToTypeMappingDefs,
getLookUpName,
getDefsFromFetchProfile,
} from '../transformers/reference_mapping'
import {
WORKFLOW_ACTION_ALERT_METADATA_TYPE,
Expand Down Expand Up @@ -211,7 +211,7 @@ const filter: FilterCreator = ({ config }) => ({
await addReferences(
elements,
buildElementsSourceForFetch(elements, config),
fieldNameToTypeMappingDefs,
getDefsFromFetchProfile(config.fetchProfile),
typesToIgnore,
createContextStrategyLookups(config.fetchProfile),
)
Expand Down
139 changes: 82 additions & 57 deletions packages/salesforce-adapter/src/transformers/reference_mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,57 +103,70 @@ type ReferenceSerializationStrategyName =
| 'mapKey'
| 'customLabel'
| 'fromDataInstance'
const ReferenceSerializationStrategyLookup: Record<ReferenceSerializationStrategyName, ReferenceSerializationStrategy> =
{
absoluteApiName: {
serialize: ({ ref, path }) => safeApiName({ ref, path, relative: false }),
lookup: val => val,
},
relativeApiName: {
serialize: ({ ref, path }) => safeApiName({ ref, path, relative: true }),
lookup: (val, context) => (context !== undefined ? [context, val].join(API_NAME_SEPARATOR) : val),
},
configurationAttributeMapping: {
serialize: async ({ ref, path }) =>
_.invert(DEFAULT_OBJECT_TO_API_MAPPING)[await safeApiName({ ref, path })] ?? safeApiName({ ref, path }),
lookup: val => (_.isString(val) ? DEFAULT_OBJECT_TO_API_MAPPING[val] ?? val : val),
},
lookupQueryMapping: {
serialize: async ({ ref, path }) =>
_.invert(TEST_OBJECT_TO_API_MAPPING)[await safeApiName({ ref, path })] ?? safeApiName({ ref, path }),
lookup: val => (_.isString(val) ? TEST_OBJECT_TO_API_MAPPING[val] ?? val : val),
| 'recordField'
export const ReferenceSerializationStrategyLookup: Record<
ReferenceSerializationStrategyName,
ReferenceSerializationStrategy
> = {
absoluteApiName: {
serialize: ({ ref, path }) => safeApiName({ ref, path, relative: false }),
lookup: val => val,
},
relativeApiName: {
serialize: ({ ref, path }) => safeApiName({ ref, path, relative: true }),
lookup: (val, context) => (context !== undefined ? [context, val].join(API_NAME_SEPARATOR) : val),
},
configurationAttributeMapping: {
serialize: async ({ ref, path }) =>
_.invert(DEFAULT_OBJECT_TO_API_MAPPING)[await safeApiName({ ref, path })] ?? safeApiName({ ref, path }),
lookup: val => (_.isString(val) ? DEFAULT_OBJECT_TO_API_MAPPING[val] ?? val : val),
},
lookupQueryMapping: {
serialize: async ({ ref, path }) =>
_.invert(TEST_OBJECT_TO_API_MAPPING)[await safeApiName({ ref, path })] ?? safeApiName({ ref, path }),
lookup: val => (_.isString(val) ? TEST_OBJECT_TO_API_MAPPING[val] ?? val : val),
},
scheduleConstraintFieldMapping: {
serialize: async ({ ref, path }) => {
const relativeApiName = await safeApiName({ ref, path, relative: true })
return _.invert(SCHEDULE_CONSTRAINT_FIELD_TO_API_MAPPING)[relativeApiName] ?? relativeApiName
},
scheduleConstraintFieldMapping: {
serialize: async ({ ref, path }) => {
const relativeApiName = await safeApiName({ ref, path, relative: true })
return _.invert(SCHEDULE_CONSTRAINT_FIELD_TO_API_MAPPING)[relativeApiName] ?? relativeApiName
},
lookup: (val, context) => {
const mappedValue = SCHEDULE_CONSTRAINT_FIELD_TO_API_MAPPING[val]
return context !== undefined ? [context, mappedValue].join(API_NAME_SEPARATOR) : mappedValue
},
lookup: (val, context) => {
const mappedValue = SCHEDULE_CONSTRAINT_FIELD_TO_API_MAPPING[val]
return context !== undefined ? [context, mappedValue].join(API_NAME_SEPARATOR) : mappedValue
},
mapKey: {
serialize: async ({ ref }) => ref.elemID.name,
lookup: val => val,
},
customLabel: {
serialize: async ({ ref, path }) => `$Label${API_NAME_SEPARATOR}${await safeApiName({ ref, path })}`,
lookup: val => {
if (val.includes('$Label')) {
return val.split(API_NAME_SEPARATOR)[1]
}
return val
},
},
mapKey: {
serialize: async ({ ref }) => ref.elemID.name,
lookup: val => val,
},
customLabel: {
serialize: async ({ ref, path }) => `$Label${API_NAME_SEPARATOR}${await safeApiName({ ref, path })}`,
lookup: val => {
if (val.includes('$Label')) {
return val.split(API_NAME_SEPARATOR)[1]
}
return val
},
fromDataInstance: {
serialize: async args =>
(await isMetadataInstanceElement(args.ref.value))
? instanceInternalId(args.ref.value)
: ReferenceSerializationStrategyLookup.absoluteApiName.serialize(args),
lookup: val => val,
},
fromDataInstance: {
serialize: async args =>
(await isMetadataInstanceElement(args.ref.value))
? instanceInternalId(args.ref.value)
: ReferenceSerializationStrategyLookup.absoluteApiName.serialize(args),
lookup: val => val,
},
recordField: {
serialize: async ({ ref, path }) =>
`Record${API_NAME_SEPARATOR}${await safeApiName({ ref, path, relative: true })}`,
lookup: (val, context) => {
if (context !== undefined && _.isString(val) && val.startsWith('Record.')) {
return [context, val.split(API_NAME_SEPARATOR)[1]].join(API_NAME_SEPARATOR)
}
return val
},
}
},
}

export type ReferenceContextStrategyName =
| 'instanceParent'
Expand Down Expand Up @@ -205,6 +218,15 @@ const FILTER_ITEM_RECORD_TYPE_FIELD_REFERENCE_DEF: FieldReferenceDefinition = {
},
}

const LIGHTNING_PAGE_FIELD_ITEM_REFERENCE_DEF: FieldReferenceDefinition = {
src: {
field: 'fieldItem',
parentTypes: ['FieldInstance'],
},
serializationStrategy: 'recordField',
target: { parentContext: 'instanceParent', type: CUSTOM_FIELD },
}

/**
* The rules for finding and resolving values into (and back from) reference expressions.
* Overlaps between rules are allowed, and the first successful conversion wins.
Expand Down Expand Up @@ -1048,27 +1070,30 @@ const getLookUpNameImpl = ({
}
}

export const getDefsFromFetchProfile = (fetchProfile: FetchProfile): FieldReferenceDefinition[] =>
fieldNameToTypeMappingDefs
.concat(
!fetchProfile.isFeatureEnabled('removeReferenceFromFilterItemToRecordType')
? [FILTER_ITEM_RECORD_TYPE_FIELD_REFERENCE_DEF]
: [],
)
.concat(
fetchProfile.isFeatureEnabled('lightningPageFieldItemReference') ? [LIGHTNING_PAGE_FIELD_ITEM_REFERENCE_DEF] : [],
)

/**
* Translate a reference expression back to its original value before deploy.
*/
export const getLookUpName = (fetchProfile: FetchProfile): GetLookupNameFunc =>
getLookUpNameImpl({
defs: fieldNameToTypeMappingDefs.concat(
!fetchProfile.isFeatureEnabled('removeReferenceFromFilterItemToRecordType')
? [FILTER_ITEM_RECORD_TYPE_FIELD_REFERENCE_DEF]
: [],
),
defs: getDefsFromFetchProfile(fetchProfile),
resolveToElementFallback: false,
defaultStrategyName: 'absoluteApiName',
})

export const getLookupNameForDataInstances = (fetchProfile: FetchProfile): GetLookupNameFunc =>
getLookUpNameImpl({
defs: fieldNameToTypeMappingDefs.concat(
!fetchProfile.isFeatureEnabled('removeReferenceFromFilterItemToRecordType')
? [FILTER_ITEM_RECORD_TYPE_FIELD_REFERENCE_DEF]
: [],
),
defs: getDefsFromFetchProfile(fetchProfile),
resolveToElementFallback: true,
defaultStrategyName: 'fromDataInstance',
})
2 changes: 2 additions & 0 deletions packages/salesforce-adapter/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export type OptionalFeatures = {
storeProfilesAndPermissionSetsBrokenPaths?: boolean
removeReferenceFromFilterItemToRecordType?: boolean
picklistsAsMaps?: boolean
lightningPageFieldItemReference?: boolean
}

export type ChangeValidatorName =
Expand Down Expand Up @@ -843,6 +844,7 @@ const optionalFeaturesType = createMatchingObjectType<OptionalFeatures>({
storeProfilesAndPermissionSetsBrokenPaths: { refType: BuiltinTypes.BOOLEAN },
removeReferenceFromFilterItemToRecordType: { refType: BuiltinTypes.BOOLEAN },
picklistsAsMaps: { refType: BuiltinTypes.BOOLEAN },
lightningPageFieldItemReference: { refType: BuiltinTypes.BOOLEAN },
},
annotations: {
[CORE_ANNOTATIONS.ADDITIONAL_PROPERTIES]: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import {
import { buildElementsSourceFromElements } from '@salto-io/adapter-utils'
import { collections } from '@salto-io/lowerdash'
import filterCreator, { addReferences, createContextStrategyLookups } from '../../src/filters/field_references'
import { fieldNameToTypeMappingDefs } from '../../src/transformers/reference_mapping'
import {
fieldNameToTypeMappingDefs,
ReferenceSerializationStrategyLookup,
} from '../../src/transformers/reference_mapping'
import {
OBJECTS_PATH,
SALESFORCE,
Expand All @@ -50,6 +53,7 @@ import { CUSTOM_OBJECT_TYPE_ID } from '../../src/filters/custom_objects_to_objec
import { defaultFilterContext } from '../utils'
import { mockTypes } from '../mock_elements'
import { FilterWith } from './mocks'
import { buildFetchProfile } from '../../src/fetch_profile/fetch_profile'

const { awu } = collections.asynciterable

Expand Down Expand Up @@ -756,3 +760,56 @@ describe('FieldReferences filter - neighbor context strategy', () => {
})
})
})

describe('Serialization Strategies', () => {
describe('recordField', () => {
const RESOLVED_VALUE = 'Record.SBQQ__Template__c'
let filter: FilterWith<'onFetch'>
let parentType: ObjectType
let instanceType: ObjectType
let instance: InstanceElement
beforeEach(() => {
parentType = mockTypes.SBQQ__LineColumn__c.clone()
instanceType = new ObjectType({
elemID: new ElemID(SALESFORCE, 'MockType'),
fields: {
fieldInstances: {
refType: new ListType(mockTypes.FieldInstance),
},
},
})
instance = createInstanceElement(
{
fullName: 'TestInstance',
fieldInstances: [
{
fieldItem: RESOLVED_VALUE,
},
],
},
instanceType,
undefined,
{
[CORE_ANNOTATIONS.PARENT]: new ReferenceExpression(parentType.elemID, parentType),
},
)
filter = filterCreator({
config: {
...defaultFilterContext,
fetchProfile: buildFetchProfile({
fetchParams: { optionalFeatures: { lightningPageFieldItemReference: true } },
}),
},
}) as FilterWith<'onFetch'>
})
it('should create reference to the CustomField and deserialize it to the original value', async () => {
await filter.onFetch([instance, instanceType, parentType, mockTypes.FieldInstance])
const createdReference = instance.value.fieldInstances[0].fieldItem as ReferenceExpression
expect(createdReference).toBeInstanceOf(ReferenceExpression)
// Make sure serialization works on the created reference
expect(
await ReferenceSerializationStrategyLookup.recordField.serialize({ ref: createdReference, element: instance }),
).toEqual(RESOLVED_VALUE)
})
})
})
9 changes: 9 additions & 0 deletions packages/salesforce-adapter/test/mock_elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,7 @@ export const mockTypes = {
SBQQ__Template__c: {
refType: Types.primitiveDataTypes.MasterDetail,
annotations: {
[API_NAME]: 'SBQQ__LineColumn__c.SBQQ__Template__c',
[FIELD_ANNOTATIONS.REFERENCE_TO]: ['SBQQ__Template__c'],
[FIELD_ANNOTATIONS.QUERYABLE]: true,
},
Expand Down Expand Up @@ -773,6 +774,14 @@ export const mockTypes = {
},
},
}),
FieldInstance: createMetadataObjectType({
annotations: {
metadataType: 'FieldInstance',
},
fields: {
fieldItem: { refType: BuiltinTypes.STRING },
},
}),
}

export const lwcJsResourceContent =
Expand Down

0 comments on commit d6f157e

Please sign in to comment.