Skip to content

Commit

Permalink
SALTO-582: custom obj translation reference fields and validation rul…
Browse files Browse the repository at this point in the history
…es (#813)
  • Loading branch information
hadard authored Mar 12, 2020
1 parent ef94963 commit f987a7d
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 13 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"packages/*"
],
"nohoist": [
"salto-vscode/**"
"vscode/**"
]
},
"devDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion packages/adapter-api/src/elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ export class InstanceElement extends Element {
* @return {InstanceElement} the cloned instance
*/
clone(): InstanceElement {
return new InstanceElement(this.elemID.name, this.type, _.cloneDeep(this.value), this.path)
return new InstanceElement(this.elemID.name, this.type, _.cloneDeep(this.value), this.path,
_.cloneDeep(this.annotations))
}
}
3 changes: 3 additions & 0 deletions packages/salesforce-adapter/src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import topicsForObjectsFilter from './filters/topics_for_objects'
import globalValueSetFilter from './filters/global_value_sets'
import instanceReferences from './filters/instance_references'
import valueSetFilter from './filters/value_set'
import customObjectTranslationFilter from './filters/custom_object_translation'
import {
FilterCreator, Filter, filtersRunner,
} from './filter'
Expand Down Expand Up @@ -84,6 +85,8 @@ export const DEFAULT_FILTERS = [
topicsForObjectsFilter,
valueSetFilter,
globalValueSetFilter,
// customObjectTranslationFilter depends on customObjectsFilter
customObjectTranslationFilter,
// The following filters should remain last in order to make sure they fix all elements
convertListsFilter,
convertTypeFilter,
Expand Down
1 change: 1 addition & 0 deletions packages/salesforce-adapter/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export const TOPICS_FOR_OBJECTS_METADATA_TYPE = 'TopicsForObjects'
export const PROFILE_METADATA_TYPE = 'Profile'
export const WORKFLOW_METADATA_TYPE = 'Workflow'
export const ASSIGNMENT_RULES_METADATA_TYPE = 'AssignmentRules'
export const VALIDATION_RULES_METADATA_TYPE = 'ValidationRule'
export const LEAD_CONVERT_SETTINGS_METADATA_TYPE = 'LeadConvertSettings'
export const QUICK_ACTION_METADATA_TYPE = 'QuickAction'
export const CUSTOM_TAB_METADATA_TYPE = 'CustomTab'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2020 Salto Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import wu from 'wu'
import {
Element, ElemID, ReferenceExpression, Field, isObjectType,
} from '@salto-io/adapter-api'
import { findInstances, findElements } from '@salto-io/adapter-utils'
import { collections } from '@salto-io/lowerdash'
import _ from 'lodash'
import { logger } from '@salto-io/logging'
import { apiName } from '../transformers/transformer'
import { FilterWith } from '../filter'
import { relativeApiName, instanceParent, parentApiNameToMetadataTypeInstances } from './utils'
import { SALESFORCE, CUSTOM_OBJECT_TRANSLATION_METADATA_TYPE, VALIDATION_RULES_METADATA_TYPE } from '../constants'

const log = logger(module)

const { makeArray } = collections.array

const FIELDS = 'fields'
const NAME = 'name'
const VALIDATION_RULES = 'validationRules'

/**
* This filter change CustomObjectTranslation logical references to fields and validation rules to
* Salto referenes
*/
const filterCreator = (): FilterWith<'onFetch'> => ({
onFetch: async (elements: Element[]) => {
const allCustomObjectFields = (elemID: ElemID): Iterable<Field> =>
wu(findElements(elements, elemID))
.filter(isObjectType)
.map(elem => Object.values(elem.fields))
.flatten()

const customToRule = parentApiNameToMetadataTypeInstances(
elements, VALIDATION_RULES_METADATA_TYPE
)

wu(findInstances(elements, new ElemID(SALESFORCE, CUSTOM_OBJECT_TRANSLATION_METADATA_TYPE)))
.forEach(customTranslation => {
const customObjectElemId = instanceParent(customTranslation)
if (_.isUndefined(customObjectElemId)) {
log.warn('failed to find custom object for custom translation %s',
apiName(customTranslation))
return
}

// Change fields to reference
const customFields = new Map(
wu(allCustomObjectFields(customObjectElemId))
.map(f => [apiName(f, true), f])
)
makeArray(customTranslation.value[FIELDS]).forEach(field => {
const customField = customFields.get(field[NAME])
if (customField) {
field[NAME] = new ReferenceExpression(customField.elemID)
} else {
log.warn('failed to find field %s in %s', field[NAME], customObjectElemId.getFullName())
}
})

// Change validation rules to refs
const objRules = new Map(
makeArray(customToRule[customObjectElemId.getFullName()])
.map(r => [relativeApiName(r), r])
)
makeArray(customTranslation.value[VALIDATION_RULES]).forEach(rule => {
const ruleInstance = objRules.get(rule[NAME])
if (ruleInstance) {
rule[NAME] = new ReferenceExpression(ruleInstance.elemID)
} else {
log.warn('failed to validation rule %s for %s', rule[NAME],
customObjectElemId.getFullName())
}
})
})
},
})

export default filterCreator
19 changes: 9 additions & 10 deletions packages/salesforce-adapter/src/filters/custom_objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
FORMULA, LEAD_CONVERT_SETTINGS_METADATA_TYPE, ASSIGNMENT_RULES_METADATA_TYPE,
WORKFLOW_METADATA_TYPE, QUICK_ACTION_METADATA_TYPE, CUSTOM_TAB_METADATA_TYPE,
DUPLICATE_RULE_METADATA_TYPE, CUSTOM_OBJECT_TRANSLATION_METADATA_TYPE,
VALIDATION_RULES_METADATA_TYPE,
} from '../constants'
import { FilterCreator } from '../filter'
import {
Expand All @@ -43,7 +44,8 @@ import {
} from '../transformers/transformer'
import {
id, addApiName, addMetadataType, addLabel, hasNamespace, getNamespace, boolValue,
buildAnnotationsObjectType, generateApiNameToCustomObject, addObjectParentReference,
buildAnnotationsObjectType, generateApiNameToCustomObject, addObjectParentReference, apiNameParts,
parentApiName,
} from './utils'
import { convertList } from './convert_lists'
import { WORKFLOW_FIELD_TO_TYPE } from './workflow'
Expand All @@ -68,7 +70,7 @@ export const NESTED_INSTANCE_VALUE_NAME = {

export const NESTED_INSTANCE_TYPE_NAME = {
WEB_LINK: 'WebLink',
VALIDATION_RULE: 'ValidationRule',
VALIDATION_RULE: VALIDATION_RULES_METADATA_TYPE,
BUSINESS_PROCESS: 'BusinessProcess',
RECORD_TYPE: 'RecordType',
LIST_VIEW: 'ListView',
Expand Down Expand Up @@ -200,7 +202,7 @@ const getFieldDependency = (values: Values): Values | undefined => {
return undefined
}

const transfromAnnotationsNames = (fields: Values, parentApiName: string): Values => {
const transfromAnnotationsNames = (fields: Values, parentName: string): Values => {
const annotations: Values = {}
const typeName = fields[INSTANCE_TYPE_FIELD]
Object.entries(fields).forEach(([k, v]) => {
Expand All @@ -209,7 +211,7 @@ const transfromAnnotationsNames = (fields: Values, parentApiName: string): Value
annotations[CORE_ANNOTATIONS.REQUIRED] = v
break
case INSTANCE_FULL_NAME_FIELD:
annotations[API_NAME] = [parentApiName, v].join(API_NAME_SEPERATOR)
annotations[API_NAME] = [parentName, v].join(API_NAME_SEPERATOR)
break
case FIELD_ANNOTATIONS.DEFAULT_VALUE:
if (typeName === FIELD_TYPE_NAMES.CHECKBOX) {
Expand Down Expand Up @@ -260,7 +262,7 @@ const transfromAnnotationsNames = (fields: Values, parentApiName: string): Value

export const transformFieldAnnotations = (
instanceFieldValues: Values,
parentApiName: string
parentName: string
): Values => {
// Ignores typeless/unknown typed instances
if (!_.has(instanceFieldValues, INSTANCE_TYPE_FIELD)) {
Expand All @@ -272,7 +274,7 @@ export const transformFieldAnnotations = (
return {}
}

const annotations = transfromAnnotationsNames(instanceFieldValues, parentApiName)
const annotations = transfromAnnotationsNames(instanceFieldValues, parentName)
const annotationsType = buildAnnotationsObjectType(fieldType.annotationTypes)
convertList(annotationsType, annotations)

Expand Down Expand Up @@ -484,9 +486,6 @@ const hasCustomObjectParent = (instance: InstanceElement): boolean =>
dependentMetadataTypes.has(metadataType(instance))

const fixDependentInstancesPathAndSetParent = (elements: Element[]): void => {
const apiNameParts = (instance: InstanceElement): string[] =>
apiName(instance).split(/\.|-/g)

const setDependingInstancePath = (instance: InstanceElement, customObject: ObjectType):
void => {
if (customObject.path) {
Expand All @@ -502,7 +501,7 @@ const fixDependentInstancesPathAndSetParent = (elements: Element[]): void => {
const apiNameToCustomObject = generateApiNameToCustomObject(elements)

const getDependentCustomObj = (instance: InstanceElement): ObjectType | undefined => {
const customObject = apiNameToCustomObject.get(apiNameParts(instance)[0])
const customObject = apiNameToCustomObject.get(parentApiName(instance))
if (_.isUndefined(customObject)
&& metadataType(instance) === LEAD_CONVERT_SETTINGS_METADATA_TYPE) {
return apiNameToCustomObject.get('Lead')
Expand Down
19 changes: 18 additions & 1 deletion packages/salesforce-adapter/src/filters/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import _ from 'lodash'
import _, { Dictionary } from 'lodash'
import { logger } from '@salto-io/logging'
import {
Element, Field, isObjectType, ObjectType, InstanceElement, isInstanceElement, isField,
Expand Down Expand Up @@ -121,6 +121,18 @@ export const buildAnnotationsObjectType = (annotationTypes: TypeMap): ObjectType
export const generateApiNameToCustomObject = (elements: Element[]): Map<string, ObjectType> =>
new Map(getCustomObjects(elements).map(obj => [apiName(obj), obj]))

export const apiNameParts = (instance: InstanceElement): string[] =>
apiName(instance).split(/\.|-/g)

export const parentApiName = (instance: InstanceElement): string =>
apiNameParts(instance)[0]

export const relativeApiName = (instance: InstanceElement): string =>
apiName(instance).slice(parentApiName(instance).length + 1)

export const instanceParent = (instance: InstanceElement): ElemID | undefined =>
instance.annotations[INSTANCE_ANNOTATIONS.PARENT]?.elemId

export const addObjectParentReference = (instance: InstanceElement,
{ elemID: objectID }: ObjectType): void => {
const instanceDeps = makeArray(instance.annotations[INSTANCE_ANNOTATIONS.PARENT])
Expand All @@ -133,3 +145,8 @@ export const addObjectParentReference = (instance: InstanceElement,

export const fullApiName = (parent: string, child: string): string =>
([parent, child].join(API_NAME_SEPERATOR))

export const parentApiNameToMetadataTypeInstances = (elements: Element[], type: string):
Dictionary<InstanceElement[]> => _(getInstancesOfMetadataType(elements, type))
.groupBy(instance => instanceParent(instance)?.getFullName())
.value() as Dictionary<InstanceElement[]>
Loading

0 comments on commit f987a7d

Please sign in to comment.