Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: warn when a flow is deleted #894

Merged
merged 10 commits into from
Jul 31, 2024
1 change: 1 addition & 0 deletions .github/linters/.cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
"sgdincludedestructive",
"shellcheck",
"sitedotcom",
"SOQL",
"staticresource",
"staticresources",
"stefanzweifel",
Expand Down
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
- [Generate a comma-separated list of the added and modified Apex classes](#generate-a-comma-separated-list-of-the-added-and-modified-apex-classes)
- [Condition deployment on package.xml and destructiveChange content](#condition-deployment-on-packagexml-and-destructivechange-content)
- [Use the module in your own node application](#use-the-module-in-your-own-node-application)
- [Handle flow deletion](#handle-flow-deletion)
- [Changelog](#changelog)
- [Built With](#built-with)
- [Versioning](#versioning)
Expand Down Expand Up @@ -529,6 +530,64 @@ console.log(JSON.stringify(work))
*/
```

### Handle flow deletion

Deleting a flow cannot be done by adding the flow in the `destructiveChanges.xml` and deploy.
A [known issue](https://issues.salesforce.com/issue/a028c00000gAwixAAC/deletion-of-flow-metadata-through-destructive-changes-not-supported) exist to cover this feature.
Please do not assume committing a flow metadata deletion to the repo, and then run sgd will allow you to delete a flow.

We suggest to deal with flow deletion in one go by following those steps (it requires the `FlowDefinition` metadata which is not available in API `v44+`)
1. Set the `FlowDefinition` `activeVersionNumber` to `0`
2. List the `FlowDefinition` in a `package.xml`
3. List all the existing version of the `Flow` in a `destructiveChangesPost.xml` (can be fetch via SOQL using this query : `SELECT FlowDefinitionView.ApiName, VersionNumber, Status FROM FlowVersionView WHERE FlowDefinitionView.ApiName='<FLOW_API_NAME>'`)
4. Deploy this `FlowDefinition` with a `package.xml` and post delete all the `Flow` versions with a post `destructiveChangesPost.xml`

Example to delete the Flow `Set_Account_Description` :
1. Set the `FlowDefinition` `activeVersionNumber` to `0`
```xml
<!--Set_Account_Description.flowDefinition-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<FlowDefinition xmlns="http://soap.sforce.com/2006/04/metadata">
<activeVersionNumber>0</activeVersionNumber>
</FlowDefinition>
```

2. List the `FlowDefinition` in a `package.xml`

```xml
<!--package.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>Set_Account_Description</members>
<name>FlowDefinition</name>
</types>
<version>61.0</version>
</Package>
```

3. List all the existing version of the `Flow` in a `destructiveChangesPost.xml`

```xml
<!--destructiveChangesPost.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>Set_Account_Description-1</members>
<members>Set_Account_Description-2</members>
<members>Set_Account_Description-...</members>
<members>Set_Account_Description-n</members>
<name>Flow</name>
</types>
</Package>
```

4. Deploy this `package.xml`, `destructiveChangesPost.xml` and `FlowDefinition`
```sh
# add `--ignore-warnings` parameter if you listed a deleted Flow version in the destructiveChangesPost.xml
sf project deploy start -x package.xml --post-destructive-changes destructiveChangesPost.xml
```

## Changelog

[changelog.md](CHANGELOG.md) is available for consultation.
Expand Down
49 changes: 49 additions & 0 deletions __tests__/unit/lib/service/flowHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict'
import { expect, jest, describe, it } from '@jest/globals'

import { DELETION } from '../../../../src/constant/gitConstants'
import { MetadataRepository } from '../../../../src/metadata/MetadataRepository'
import FlowHandler from '../../../../src/service/flowHandler'
import type { Work } from '../../../../src/types/work'
import { getGlobalMetadata, getWork } from '../../../__utils__/globalTestHelper'

jest.mock('../../../../src/utils/fsHelper')

const objectType = {
directoryName: 'flows',
inFolder: false,
metaFile: false,
suffix: 'flow',
xmlName: 'Flow',
}
const basePath = `force-app/main/default/${objectType.directoryName}`
let work: Work
beforeEach(() => {
jest.clearAllMocks()
work = getWork()
})

describe('flowHandler', () => {
let globalMetadata: MetadataRepository
beforeAll(async () => {
globalMetadata = await getGlobalMetadata()
})
describe('when a flow is deleted', () => {
it('warns the user not to', async () => {
// Arrange
const sut = new FlowHandler(
`${DELETION} ${basePath}/MyFlow.${objectType.suffix}-meta.xml`,
objectType,
work,
globalMetadata
)
expect(work.warnings.length).toBe(0)

// Act
await sut.handle()

// Assert
expect(work.warnings.length).toBe(1)
})
})
})
9 changes: 9 additions & 0 deletions __tests__/unit/lib/service/typeHandlerFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { expect, describe, it } from '@jest/globals'
import { MetadataRepository } from '../../../../src/metadata/MetadataRepository'
import CustomField from '../../../../src/service/customFieldHandler'
import Decomposed from '../../../../src/service/decomposedHandler'
import FlowHandler from '../../../../src/service/flowHandler'
import InFolder from '../../../../src/service/inFolderHandler'
import InResource from '../../../../src/service/inResourceHandler'
import SharedFolder from '../../../../src/service/sharedFolderHandler'
Expand Down Expand Up @@ -74,6 +75,14 @@ describe('the type handler factory', () => {
).toBeInstanceOf(InFolder)
})

it('can handle Flow', () => {
expect(
typeHandlerFactory.getTypeHandler(
`Z force-app/main/default/flows/MyFlow.flow-meta.xml`
)
).toBeInstanceOf(FlowHandler)
})

it.each([
'force-app/main/default/TestClass.cls',
'force-app/main/default/TestClass.cls-meta.xml',
Expand Down
34 changes: 17 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@
"author": "Sebastien Colladon <[email protected]>",
"dependencies": {
"@salesforce/command": "^5.3.9",
"@salesforce/core": "^8.1.1",
"@salesforce/core": "^8.2.6",
"async": "^3.2.5",
"fast-xml-parser": "^4.4.0",
"fast-xml-parser": "^4.4.1",
"fs-extra": "^11.2.0",
"ignore": "^5.3.1",
"isomorphic-git": "^1.27.0",
"isomorphic-git": "^1.27.1",
"lodash": "^4.17.21",
"simple-git": "^3.25.0",
"xmlbuilder2": "^3.1.1"
Expand Down Expand Up @@ -224,38 +224,38 @@
"@jest/globals": "^29.7.0",
"@ls-lint/ls-lint": "^2.2.3",
"@oclif/dev-cli": "^1.26.10",
"@salesforce/cli-plugins-testkit": "^5.3.18",
"@salesforce/cli-plugins-testkit": "^5.3.20",
"@salesforce/dev-config": "^4.1.0",
"@salesforce/ts-sinon": "^1.4.22",
"@salesforce/ts-sinon": "^1.4.23",
"@stryker-mutator/core": "^8.2.6",
"@stryker-mutator/jest-runner": "^8.2.6",
"@swc/core": "^1.6.13",
"@swc/core": "^1.7.3",
"@types/async": "^3.2.24",
"@types/jest": "^29.5.12",
"@types/mocha": "^10.0.7",
"@types/node": "^20.14.10",
"@typescript-eslint/eslint-plugin": "^7.15.0",
"@typescript-eslint/parser": "^7.15.0",
"@types/node": "^22.0.0",
"@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.17.0",
"benchmark": "^2.1.4",
"chai": "^4.3.10",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"husky": "^9.0.11",
"eslint-plugin-prettier": "^5.2.1",
"husky": "^9.1.4",
"jest": "^29.7.0",
"knip": "^5.24.1",
"knip": "^5.27.0",
"lint-staged": "^15.2.7",
"mocha": "^10.6.0",
"mocha": "^10.7.0",
"nyc": "^17.0.0",
"prettier": "^3.3.2",
"prettier": "^3.3.3",
"shx": "^0.3.4",
"sinon": "^18.0.0",
"ts-jest": "^29.1.5",
"ts-jest": "^29.2.3",
"ts-node": "^10.9.2",
"tslib": "^2.6.3",
"typescript": "^5.5.3",
"wireit": "^0.14.4",
"typescript": "^5.5.4",
"wireit": "^0.14.5",
"yarn-audit-fix": "^10.0.7",
"yarn-upgrade-all": "^0.7.2"
},
Expand Down
1 change: 1 addition & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const message = {
errorPathIsNotFile: `'%s' file does not exist`,
errorPathIsNotGit: `'%s' is not a git repository`,
warningApiVersionNotSupported: `API version not found or not supported, using '%s' instead`,
warningFlowDeleted: `Attempt to delete the flow '%s' via destructiveChanges.xml may not work as expected (see https://github.com/scolladon/sfdx-git-delta#handle-flow-deletion)`,
}

export default message
19 changes: 19 additions & 0 deletions src/service/flowHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict'
import { format } from 'util'

import messages from '../locales/en'

import StandardHandler from './standardHandler'

export default class FlowHandler extends StandardHandler {
public override async handleDeletion() {
await super.handleDeletion()
this.warnFlowDeleted()
}

private warnFlowDeleted() {
this.work.warnings.push(
new Error(format(messages.warningFlowDeleted, this._getElementName()))
)
}
}
2 changes: 2 additions & 0 deletions src/service/typeHandlerFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import CustomFieldHandler from './customFieldHandler'
import CustomLabel from './customLabelHandler'
import CustomObject from './customObjectHandler'
import Decomposed from './decomposedHandler'
import FlowHandler from './flowHandler'
import InBundle from './inBundleHandler'
import InFile from './inFileHandler'
import InFolder from './inFolderHandler'
Expand Down Expand Up @@ -35,6 +36,7 @@ const handlerMap = {
EscalationRules: InFile,
ExperienceBundle: InResource,
FieldSet: Decomposed,
Flow: FlowHandler,
GlobalValueSetTranslation: InFile,
Index: Decomposed,
LightningComponentBundle: Lwc,
Expand Down
Loading
Loading