diff --git a/README.md b/README.md index c4df1a6c..a6242134 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,13 @@ Here is a full blown example showing most of the supported settings in action: "value": "CD", "newValue": "Media", "absent": true + }, + { + "metadataType": "CustomField", + "metadataFullName": "Vehicle__c.Features__c", + "value": "CD", + "newValue": "AC", + "active": false } ] }, diff --git a/src/plugins/picklists/activate.json b/src/plugins/picklists/activate.json new file mode 100644 index 00000000..69a456f5 --- /dev/null +++ b/src/plugins/picklists/activate.json @@ -0,0 +1,15 @@ +{ + "$schema": "../schema.json", + "settings": { + "picklists": { + "picklistValues": [ + { + "metadataType": "CustomField", + "metadataFullName": "Vehicle__c.Features__c", + "value": "AC", + "active": true + } + ] + } + } +} diff --git a/src/plugins/picklists/deactivate.json b/src/plugins/picklists/deactivate.json new file mode 100644 index 00000000..fc7c91e9 --- /dev/null +++ b/src/plugins/picklists/deactivate.json @@ -0,0 +1,15 @@ +{ + "$schema": "../schema.json", + "settings": { + "picklists": { + "picklistValues": [ + { + "metadataType": "CustomField", + "metadataFullName": "Vehicle__c.Features__c", + "value": "AC", + "active": false + } + ] + } + } +} diff --git a/src/plugins/picklists/index.e2e-spec.ts b/src/plugins/picklists/index.e2e-spec.ts index 4a11e618..2bea79ee 100644 --- a/src/plugins/picklists/index.e2e-spec.ts +++ b/src/plugins/picklists/index.e2e-spec.ts @@ -55,4 +55,40 @@ describe(Picklists.name, function() { replaceCmd.output.toString() ); }); + it('should deactivate picklist value', () => { + const cmd = child.spawnSync(path.resolve('bin', 'run'), [ + 'browserforce:apply', + '-f', + path.resolve(path.join(__dirname, 'deactivate.json')) + ]); + assert.deepStrictEqual(cmd.status, 0, cmd.output.toString()); + assert( + /changing 'picklistValues' to.*/.test(cmd.output.toString()), + cmd.output.toString() + ); + }); + it('should activate picklist value', () => { + const cmd = child.spawnSync(path.resolve('bin', 'run'), [ + 'browserforce:apply', + '-f', + path.resolve(path.join(__dirname, 'activate.json')) + ]); + assert.deepStrictEqual(cmd.status, 0, cmd.output.toString()); + assert( + /changing 'picklistValues' to.*/.test(cmd.output.toString()), + cmd.output.toString() + ); + }); + it('should not do anything when the picklist values do not exist', () => { + const cmd = child.spawnSync(path.resolve('bin', 'run'), [ + 'browserforce:apply', + '-f', + path.resolve(path.join(__dirname, 'activate.json')) + ]); + assert.deepStrictEqual(cmd.status, 0, cmd.output.toString()); + assert( + /no action necessary/.test(cmd.output.toString()), + cmd.output.toString() + ); + }); }); diff --git a/src/plugins/picklists/index.ts b/src/plugins/picklists/index.ts index ed45ee86..85a51c81 100644 --- a/src/plugins/picklists/index.ts +++ b/src/plugins/picklists/index.ts @@ -49,7 +49,12 @@ export default class Picklists extends BrowserforcePlugin { ); const page = await this.browserforce.openPage(picklistUrl); const picklistPage = new PicklistPage(page); - if (action.absent) { + if (action.active !== undefined) { + await picklistPage.clickActivateDeactivateActionForValue( + action.value, + action.active + ); + } else if (action.absent) { const replacePage = await picklistPage.clickDeleteActionForValue( action.value ); @@ -104,10 +109,16 @@ function isActionRequired(action, values) { const valueGiven = action.value !== undefined && action.value !== null; const newValueGiven = action.newValue !== undefined && action.newValue !== null; - if (valueGiven && !values.includes(action.value)) { - return false; + if (valueGiven) { + const match = values.find(x => x.value === action.value); + if (!match) { + return false; + } + if (action.active !== undefined && action.active === match.active) { + return false; + } } - if (newValueGiven && !values.includes(action.newValue)) { + if (newValueGiven && !values.find(x => x.value === action.newValue)) { return false; } return true; diff --git a/src/plugins/picklists/pages.ts b/src/plugins/picklists/pages.ts index 45638fb4..34d2b366 100644 --- a/src/plugins/picklists/pages.ts +++ b/src/plugins/picklists/pages.ts @@ -7,6 +7,11 @@ import { JSHandle } from 'puppeteer'; // - label column is a th (which is not used) // - xpath indices are 1 based +type PicklistValue = { + value: string; + active: boolean; +}; + export class PicklistPage { private page; @@ -14,17 +19,33 @@ export class PicklistPage { this.page = page; } - public async getPicklistValues(): Promise> { - const xpath = `//tr[td[1]//a[contains(@href, "/setup/ui/picklist_masteredit") or contains(@href, "/setup/ui/picklist_masterdelete")]]//td[2]`; - await this.page.waitForXPath(xpath); - const fullNameHandles = await this.page.$x(xpath); - const innerTextJsHandles = await Promise.all( - fullNameHandles.map(handle => handle.getProperty('innerText')) + public async getPicklistValues(): Promise> { + // wait for New button in any related list + await this.page.waitForSelector('body table input[name="new"]'); + const resolvePicklistValueNames = async xpath => { + const fullNameHandles = await this.page.$x(xpath); + const innerTextJsHandles = await Promise.all( + fullNameHandles.map(handle => handle.getProperty('innerText')) + ); + const fullNames = await Promise.all( + innerTextJsHandles.map(handle => handle.jsonValue()) + ); + return fullNames; + }; + const active = await resolvePicklistValueNames( + `//tr[td[1]//a[contains(@href, "/setup/ui/picklist_masteredit")]]//td[2]` ); - const fullNames = await Promise.all( - innerTextJsHandles.map(handle => handle.jsonValue()) + const inactive = await resolvePicklistValueNames( + `//tr[td[1]//a[contains(@href, "/setup/ui/picklist_masteractivate")]]//td[2]` ); - return fullNames; + return [ + ...active.map(x => { + return { value: x, active: true }; + }), + ...inactive.map(x => { + return { value: x, active: false }; + }) + ]; } public async clickReplaceActionButton(): Promise { @@ -57,6 +78,36 @@ export class PicklistPage { ]); return new PicklistReplaceAndDeletePage(this.page); } + + public async clickActivateDeactivateActionForValue( + picklistValueApiName: string, + active: boolean + ): Promise { + let xpath; + let actionName; + if (active) { + xpath = `//tr[td[2][text() = "${picklistValueApiName}"]]//td[1]//a[contains(@href, "/setup/ui/picklist_masteractivate.jsp")]`; + actionName = 'activate'; + } else { + xpath = `//tr[td[2][text() = "${picklistValueApiName}"]]//td[1]//a[contains(@href, "/setup/ui/picklist_masterdelete.jsp") and contains(@href, "deleteType=1")]`; + actionName = 'deactivate'; + } + await this.page.waitForXPath(xpath); + const actionLinkHandles = await this.page.$x(xpath); + if (actionLinkHandles.length !== 1) { + throw new Error( + `Could not find ${actionName} action for picklist value: ${picklistValueApiName}` + ); + } + this.page.on('dialog', async dialog => { + await dialog.accept(); + }); + await Promise.all([ + this.page.waitForNavigation(), + actionLinkHandles[0].click() + ]); + return this.page; + } } export class PicklistReplacePage { diff --git a/src/plugins/picklists/schema.json b/src/plugins/picklists/schema.json index 9e641cd4..e25a619f 100644 --- a/src/plugins/picklists/schema.json +++ b/src/plugins/picklists/schema.json @@ -37,6 +37,10 @@ "type": "boolean", "description": "replace all blank values (mutually exclusive to replacing an old value)" }, + "active": { + "type": "boolean", + "description": "ensure the picklist value is active/inactive" + }, "absent": { "type": "boolean", "description": "ensure the picklist value is absent/deleted"