Skip to content

Commit

Permalink
SALTO-4402 - Salesforce: Adapter config to control profile references (
Browse files Browse the repository at this point in the history
  • Loading branch information
tomsellek authored Jun 30, 2023
1 parent 18aa73e commit ace9f39
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 33 deletions.
17 changes: 12 additions & 5 deletions packages/salesforce-adapter/src/filters/field_references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ import { logger } from '@salto-io/logging'
import { collections, multiIndex } from '@salto-io/lowerdash'
import { apiName, metadataType } from '../transformers/transformer'
import { LocalFilterCreator } from '../filter'
import { generateReferenceResolverFinder, ReferenceContextStrategyName, FieldReferenceDefinition, getLookUpName, fieldNameToTypeMappingDefs, defaultFieldNameToTypeMappingDefs } from '../transformers/reference_mapping'
import {
generateReferenceResolverFinder,
ReferenceContextStrategyName,
FieldReferenceDefinition,
getLookUpName,
getReferenceMappingDefs,
} from '../transformers/reference_mapping'
import {
WORKFLOW_ACTION_ALERT_METADATA_TYPE, WORKFLOW_FIELD_UPDATE_METADATA_TYPE,
WORKFLOW_FLOW_ACTION_METADATA_TYPE, WORKFLOW_OUTBOUND_MESSAGE_METADATA_TYPE,
Expand Down Expand Up @@ -105,7 +111,7 @@ const contextStrategyLookup: Record<
export const addReferences = async (
elements: Element[],
referenceElements: ReadOnlyElementsSource,
defs?: FieldReferenceDefinition[]
defs: FieldReferenceDefinition[]
): Promise<void> => {
const resolverFinder = generateReferenceResolverFinder(defs)

Expand Down Expand Up @@ -151,9 +157,10 @@ export const addReferences = async (
const filter: LocalFilterCreator = ({ config }) => ({
name: 'fieldReferencesFilter',
onFetch: async elements => {
const refDef = config.enumFieldPermissions
? defaultFieldNameToTypeMappingDefs
: fieldNameToTypeMappingDefs
const refDef = getReferenceMappingDefs({
enumFieldPermissions: config.enumFieldPermissions ?? false,
fetchProfiles: !config.fetchProfile.isFeatureEnabled('ignoreRefsInProfiles'),
})
await addReferences(
elements,
buildElementsSourceForFetch(elements, config),
Expand Down
77 changes: 51 additions & 26 deletions packages/salesforce-adapter/src/transformers/reference_mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,37 +244,17 @@ export const defaultFieldNameToTypeMappingDefs: FieldReferenceDefinition[] = [
target: { type: 'ApexPage' },
},
{
src: { field: 'apexClass', parentTypes: ['FlowApexPluginCall', 'FlowVariable', 'ProfileApexClassAccess', 'TransactionSecurityPolicy'] },
src: { field: 'apexClass', parentTypes: ['FlowApexPluginCall', 'FlowVariable', 'TransactionSecurityPolicy'] },
target: { type: 'ApexClass' },
},
{
src: { field: 'recipient', parentTypes: ['WorkflowEmailRecipient'] },
target: { type: 'Role' },
},
{
src: { field: 'application', parentTypes: ['ProfileApplicationVisibility'] },
target: { type: 'CustomApplication' },
},
{
src: { field: 'permissionSets', parentTypes: ['PermissionSetGroup', 'DelegateGroup'] },
target: { type: 'PermissionSet' },
},
{
src: { field: 'layout', parentTypes: ['ProfileLayoutAssignment'] },
target: { type: 'Layout' },
},
{
src: { field: 'recordType', parentTypes: ['ProfileLayoutAssignment'] },
target: { type: 'RecordType' },
},
{
src: { field: 'flow', parentTypes: ['ProfileFlowAccess'] },
target: { type: 'Flow' },
},
{
src: { field: 'recordType', parentTypes: ['ProfileRecordTypeVisibility'] },
target: { type: 'RecordType' },
},
{
src: { field: 'tabs', parentTypes: ['CustomApplication'] },
target: { type: 'CustomTab' },
Expand Down Expand Up @@ -309,7 +289,7 @@ export const defaultFieldNameToTypeMappingDefs: FieldReferenceDefinition[] = [
target: { type: CUSTOM_OBJECT },
},
{
src: { field: 'object', parentTypes: ['ProfileObjectPermissions', 'FlowDynamicChoiceSet', 'FlowRecordLookup', 'FlowRecordUpdate', 'FlowRecordCreate', 'FlowRecordDelete', 'FlowStart', 'PermissionSetObjectPermissions'] },
src: { field: 'object', parentTypes: ['FlowDynamicChoiceSet', 'FlowRecordLookup', 'FlowRecordUpdate', 'FlowRecordCreate', 'FlowRecordDelete', 'FlowStart', 'PermissionSetObjectPermissions'] },
target: { type: CUSTOM_OBJECT },
},
{
Expand Down Expand Up @@ -675,13 +655,44 @@ export const defaultFieldNameToTypeMappingDefs: FieldReferenceDefinition[] = [
]

// Optional reference that should not be used if enumFieldPermissions config is on
const fieldPermissionEnumDisabledExtraMappingDefs: FieldReferenceDefinition[] = [
export const fieldPermissionEnumDisabledExtraMappingDefs: FieldReferenceDefinition[] = [
{
src: { field: 'field', parentTypes: ['ProfileFieldLevelSecurity'] },
target: { type: CUSTOM_FIELD },
},
]

export const referencesFromProfile: FieldReferenceDefinition[] = [
{
src: { field: 'object', parentTypes: ['ProfileObjectPermissions'] },
target: { type: CUSTOM_OBJECT },
},
{
src: { field: 'apexClass', parentTypes: ['ProfileApexClassAccess'] },
target: { type: 'ApexClass' },
},
{
src: { field: 'layout', parentTypes: ['ProfileLayoutAssignment'] },
target: { type: 'Layout' },
},
{
src: { field: 'recordType', parentTypes: ['ProfileLayoutAssignment'] },
target: { type: 'RecordType' },
},
{
src: { field: 'flow', parentTypes: ['ProfileFlowAccess'] },
target: { type: 'Flow' },
},
{
src: { field: 'recordType', parentTypes: ['ProfileRecordTypeVisibility'] },
target: { type: 'RecordType' },
},
{
src: { field: 'application', parentTypes: ['ProfileApplicationVisibility'] },
target: { type: 'CustomApplication' },
},
]

/**
* 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 All @@ -693,11 +704,25 @@ const fieldPermissionEnumDisabledExtraMappingDefs: FieldReferenceDefinition[] =
* 1. An element matching the rule is found.
* 2. Resolving the resulting reference expression back returns the original value.
*/
export const fieldNameToTypeMappingDefs: FieldReferenceDefinition[] = [
const fieldNameToTypeMappingDefs: FieldReferenceDefinition[] = [
...defaultFieldNameToTypeMappingDefs,
...fieldPermissionEnumDisabledExtraMappingDefs,
...referencesFromProfile,
]

export const getReferenceMappingDefs = (
args: {enumFieldPermissions: boolean; fetchProfiles: boolean }
): FieldReferenceDefinition[] => {
let refDefs = defaultFieldNameToTypeMappingDefs
if (args.enumFieldPermissions) {
refDefs = refDefs.concat(fieldPermissionEnumDisabledExtraMappingDefs)
}
if (args.fetchProfiles) {
refDefs = refDefs.concat(referencesFromProfile)
}
return refDefs
}

const matchName = (name: string, matcher: string | RegExp): boolean => (
_.isString(matcher)
? matcher === name
Expand Down Expand Up @@ -756,7 +781,7 @@ export type ReferenceResolverFinder = (
* Generates a function that filters the relevant resolvers for a given field.
*/
export const generateReferenceResolverFinder = (
defs = fieldNameToTypeMappingDefs
defs: FieldReferenceDefinition[],
): ReferenceResolverFinder => {
const referenceDefinitions = defs.map(
def => FieldReferenceResolver.create(def)
Expand Down Expand Up @@ -829,4 +854,4 @@ const getLookUpNameImpl = (defs = fieldNameToTypeMappingDefs): GetLookupNameFunc
/**
* Translate a reference expression back to its original value before deploy.
*/
export const getLookUpName = getLookUpNameImpl()
export const getLookUpName = getLookUpNameImpl(fieldNameToTypeMappingDefs)
2 changes: 2 additions & 0 deletions packages/salesforce-adapter/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export type OptionalFeatures = {
skipAliases?: boolean
formulaDeps?: boolean
fetchCustomObjectUsingRetrieveApi?: boolean
ignoreRefsInProfiles?: boolean
}

export type ChangeValidatorName = (
Expand Down Expand Up @@ -574,6 +575,7 @@ const optionalFeaturesType = createMatchingObjectType<OptionalFeatures>({
skipAliases: { refType: BuiltinTypes.BOOLEAN },
formulaDeps: { refType: BuiltinTypes.BOOLEAN },
fetchCustomObjectUsingRetrieveApi: { refType: BuiltinTypes.BOOLEAN },
ignoreRefsInProfiles: { refType: BuiltinTypes.BOOLEAN },
},
annotations: {
[CORE_ANNOTATIONS.ADDITIONAL_PROPERTIES]: false,
Expand Down
64 changes: 62 additions & 2 deletions packages/salesforce-adapter/test/filters/field_references.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import { ElemID, InstanceElement, ObjectType, ReferenceExpression, Element, Buil
import { buildElementsSourceFromElements } from '@salto-io/adapter-utils'
import { collections } from '@salto-io/lowerdash'
import filterCreator, { addReferences } from '../../src/filters/field_references'
import { fieldNameToTypeMappingDefs } from '../../src/transformers/reference_mapping'
import {
FieldReferenceDefinition,
getReferenceMappingDefs,
} from '../../src/transformers/reference_mapping'
import { OBJECTS_PATH, SALESFORCE, CUSTOM_OBJECT, METADATA_TYPE, INSTANCE_FULL_NAME_FIELD, CUSTOM_OBJECT_ID_FIELD, API_NAME, API_NAME_SEPARATOR, WORKFLOW_ACTION_REFERENCE_METADATA_TYPE, WORKFLOW_RULE_METADATA_TYPE, CPQ_QUOTE_LINE_FIELDS, CPQ_CUSTOM_SCRIPT, CPQ_CONFIGURATION_ATTRIBUTE, CPQ_DEFAULT_OBJECT_FIELD, CPQ_LOOKUP_QUERY, CPQ_TESTED_OBJECT, CPQ_DISCOUNT_SCHEDULE, CPQ_CONSTRAINT_FIELD } from '../../src/constants'
import { metadataType, apiName, createInstanceElement } from '../../src/transformers/transformer'
import { CUSTOM_OBJECT_TYPE_ID } from '../../src/filters/custom_objects_to_object_type'
Expand Down Expand Up @@ -485,7 +488,7 @@ describe('FieldReferences filter', () => {

beforeAll(async () => {
elements = generateElements()
const modifiedDefs = fieldNameToTypeMappingDefs.map(def => _.omit(def, 'serializationStrategy'))
const modifiedDefs = getReferenceMappingDefs({ enumFieldPermissions: false, fetchProfiles: false }).map(def => _.omit(def, 'serializationStrategy'))
await addReferences(elements, buildElementsSourceFromElements(elements), modifiedDefs)
})
afterAll(() => {
Expand Down Expand Up @@ -741,3 +744,60 @@ describe('FieldReferences filter - neighbor context strategy', () => {
})
})
})

describe('getReferenceMappingDefs', () => {
let defs: FieldReferenceDefinition[]

describe('Without any optional defs', () => {
beforeEach(() => {
defs = getReferenceMappingDefs({ enumFieldPermissions: false, fetchProfiles: false })
})
it('should not have any profile-related references', () => {
defs
.flatMap(def => def.src.parentTypes)
.forEach(type => expect(type).not.toInclude('Profile'))
})
})
describe('With profile-related optional defs', () => {
beforeEach(() => {
defs = getReferenceMappingDefs({ enumFieldPermissions: false, fetchProfiles: true })
})
it('should have profile-related references, but not FLS', () => {
const profileRelatedDefs = defs
.flatMap(def => def.src.parentTypes)
.filter(type => type.includes('Profile'))
expect(profileRelatedDefs).not.toBeEmpty()

profileRelatedDefs
.forEach(type => expect(type).not.toInclude('ProfileFieldLevelSecurity'))
})
})
describe('With FLS-related optional defs', () => {
beforeEach(() => {
defs = getReferenceMappingDefs({ enumFieldPermissions: true, fetchProfiles: false })
})
it('should have only FLS-related profile references', () => {
const profileRelatedDefs = defs
.flatMap(def => def.src.parentTypes)
.filter(type => type.includes('Profile'))
expect(profileRelatedDefs).not.toBeEmpty()

profileRelatedDefs
.forEach(type => expect(type).toInclude('ProfileFieldLevelSecurity'))
})
})
describe('With all optional defs', () => {
beforeEach(() => {
defs = getReferenceMappingDefs({ enumFieldPermissions: true, fetchProfiles: true })
})
it('should have only all profile references', () => {
const profileRelatedDefs = defs
.flatMap(def => def.src.parentTypes)
.filter(type => type.includes('Profile'))
const flsRelatedDefs = profileRelatedDefs
.filter(type => type.includes('ProfileFieldLevelSecurity'))
expect(flsRelatedDefs).not.toBeEmpty()
expect(profileRelatedDefs.length).toBeGreaterThan(flsRelatedDefs.length)
})
})
})

0 comments on commit ace9f39

Please sign in to comment.