Skip to content

Commit

Permalink
Merge 0432b3a into feature/248__hh_container
Browse files Browse the repository at this point in the history
  • Loading branch information
salesforce-org-metaci[bot] authored Nov 6, 2023
2 parents f0eed10 + 0432b3a commit 01ce129
Show file tree
Hide file tree
Showing 12 changed files with 363 additions and 0 deletions.
27 changes: 27 additions & 0 deletions force-app/main/default/classes/CON_ContactMerge_CTRL.cls
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ public with sharing class CON_ContactMerge_CTRL {
*/
public Map<Id, Integer> contactCountByDuplicateRecordSetId { get; set; }

/***********************************************************************************************
* @description A Set of Contact Ids passed into Contact Merge that can be used to
* return search results
*/
private Set<Id> contactIdsToSearch;

/***********************************************************************************************
* @description Checks whether there are more records to display on next page in pagination
* implemented on Duplicate Record Set diplay page.
Expand Down Expand Up @@ -476,6 +482,25 @@ public with sharing class CON_ContactMerge_CTRL {
ApexPages.addMessages(e);
}
}
//otherwise, check for a searchIds parameter, which should contain a comma separated list
//of Ids to search
else if(ApexPages.CurrentPage().getParameters().containsKey('searchIds') &&
ApexPages.CurrentPage().getParameters().get('searchIds') != '') {
try {
contactIdsToSearch = new set<Id>((list<Id>)ApexPages.CurrentPage().getParameters()
.get('searchIds').split(','));
if (contactIdsToSearch != null) {
loadMergePage = true;
search();
if(!ApexPages.hasMessages()) {
showContactSearch = true;
}
}
}
catch(StringException e){
ApexPages.addMessages(e);
}
}
}

/***********************************************************************************************
Expand Down Expand Up @@ -917,6 +942,8 @@ public with sharing class CON_ContactMerge_CTRL {
//build the SOSL query and execute - NOTE: * wildcard will only have effect at the
//middle or end of the search term
return mergeSelector.selectContactsByName(searchText);
} else if(contactIdsToSearch != null) {
return mergeSelector.selectContactsById(contactIdsToSearch);
} else {
return mergeSelector.selectDuplicateRecordSetById(drsRecordId, driFieldNames);
}
Expand Down
39 changes: 39 additions & 0 deletions force-app/main/default/classes/PotentialDuplicates.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
public with sharing class PotentialDuplicates {

private static final String SET_OF_MATCHES_KEY = 'setOfMatches';

@AuraEnabled
public static Map<String, Object> getDuplicates(Id recordId) {
Map<String, Object> returnParams = new Map<String, Object>{ SET_OF_MATCHES_KEY => null };

try {
returnParams.put(SET_OF_MATCHES_KEY, getDuplicateList(recordId));
}
catch (Exception e) {
}

return returnParams;
}

private static String getDuplicateList(Id recordId) {
String strSetOfMatches = '';

Set<String> setOfMatchIds = new Set<String>();
List<Datacloud.FindDuplicatesResult> results =
Datacloud.FindDuplicatesByIds.findDuplicatesByIds(new List<Id>{recordId});
for (Datacloud.FindDuplicatesResult findDupeResult : results) {
for (Datacloud.DuplicateResult dupeResult : findDupeResult.getDuplicateResults()) {
for (Datacloud.MatchResult matchResult : dupeResult.getMatchResults()) {
for (Datacloud.MatchRecord matchRecord : matchResult.getMatchRecords()) {
setOfMatchIds.add(matchRecord.getRecord().Id);
}
}
}
}
for (String matchId : setOfMatchIds) {
strSetOfMatches += (strSetOfMatches == '' ? '' : ',') + matchId;
}

return strSetOfMatches;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<status>Active</status>
</ApexClass>
28 changes: 28 additions & 0 deletions force-app/main/default/classes/PotentialDuplicates_TEST.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@IsTest(IsParallel=false)
private with sharing class PotentialDuplicates_TEST {

@IsTest
private static void shouldReturnNullWhenNoDuplicatesAreFound() {
Id recordId = UTIL_UnitTestData_TEST.mockId(Contact.getSObjectType());
Map<String, Object> data = PotentialDuplicates.getDuplicates(recordId);
System.assertEquals('', data.get('setOfMatches'),
'There should be no duplicates');
}

@IsTest
private static void shouldReturnIdsWhenDuplicatesAreFound() {
List<Contact> contactList = UTIL_UnitTestData_TEST.getContacts(3);
for(Contact c : contactList) {
c.FirstName = 'Test';
c.LastName = 'LastName';
c.Email = '[email protected]';
}
insert contactList;

Map<String, Object> data = PotentialDuplicates.getDuplicates(contactList[0].Id);
String setOfMatches = (String)data.get('setOfMatches');
System.assertNotEquals('', setOfMatches, 'Duplicate Ids should be returned');
Integer numberOfMatches = setOfMatches.split(',').size();
System.assertEquals(2, numberOfMatches, 'There should be 2 duplicates returned');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<status>Active</status>
</ApexClass>
32 changes: 32 additions & 0 deletions force-app/main/default/labels/CustomLabels.labels-meta.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12698,6 +12698,30 @@ If preferred, you can disable Gift Entry and instead use older Batch Gift Entry
<shortDescription>pmtWrittenOffPaymentDateRequired</shortDescription>
<value>A written off Payment must have a Payment Date.</value>
</labels>
<labels>
<fullName>potentialDuplicatesFoundNone</fullName>
<categories>Potential Duplicates</categories>
<language>en_US</language>
<protected>false</protected>
<shortDescription>No duplicates found</shortDescription>
<value>We found no potential duplicates of this Contact.</value>
</labels>
<labels>
<fullName>potentialDuplicatesFoundOne</fullName>
<categories>Potential Duplicates</categories>
<language>en_US</language>
<protected>false</protected>
<shortDescription>One potential duplicate found</shortDescription>
<value>We found 1 potential duplicate of this Contact.</value>
</labels>
<labels>
<fullName>potentialDuplicatesFoundMultiple</fullName>
<categories>Potential Duplicates</categories>
<language>en_US</language>
<protected>false</protected>
<shortDescription>Number of potential duplicates found</shortDescription>
<value>We found {0} potential duplicates of this Contact.</value>
</labels>
<labels>
<fullName>psACH</fullName>
<categories>Payment Gateway Management</categories>
Expand Down Expand Up @@ -16396,4 +16420,12 @@ Note: The NPSP Settings tab is visible in the Nonprofit Success Pack application
<shortDescription>Label for modifications on Recurring Donations</shortDescription>
<value>Update Recurring Donation</value>
</labels>
<labels>
<fullName>viewDuplicates</fullName>
<categories>Potential Duplicates</categories>
<language>en_US</language>
<protected>true</protected>
<shortDescription>Label for View Duplicates link</shortDescription>
<value>View Duplicates</value>
</labels>
</CustomLabels>
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { createElement } from 'lwc';
import PotentialDuplicates from 'c/potentialDuplicates';
import getDuplicates from '@salesforce/apex/PotentialDuplicates.getDuplicates';
import lblPotentialDuplicatesFoundNone from '@salesforce/label/c.potentialDuplicatesFoundNone';
import lblPotentialDuplicatesFoundOne from '@salesforce/label/c.potentialDuplicatesFoundOne';
import lblPotentialDuplicatesFoundMultiple from '@salesforce/label/c.potentialDuplicatesFoundMultiple';
import lblViewDuplicates from '@salesforce/label/c.viewDuplicates';

jest.mock('@salesforce/apex/PotentialDuplicates.getDuplicates',
() => ({ default : jest.fn() }),
{ virtual: true }
);

const createComponentForTest = () => {
const el = createElement('c-potential-duplicates', {
is: PotentialDuplicates
});
Object.assign(el);
el.recordId = 'fakeId';
el.displayCard = true;
document.body.appendChild(el);
return el;
};

describe('Potential Duplicates Component', () => {

afterEach(() => {
clearDOM();
jest.clearAllMocks();
});

it('renders the component with no link', async () => {
getDuplicates.mockResolvedValue({});
const el = createComponentForTest();
await flushPromises();

const heading = el.shadowRoot.querySelector('h1');
expect(heading.textContent).toBe(lblPotentialDuplicatesFoundNone);
const duplicateLink = el.shadowRoot.querySelector('[data-qa-locator="viewDuplicatesURL"]');
expect(duplicateLink).toBeFalsy();
});

it('renders the component with a duplicate link', async () => {
getDuplicates.mockResolvedValue({"setOfMatches" : "anotherId"});
const el = createComponentForTest();
await flushPromises();

expect(getDuplicates).toHaveBeenCalled();
const heading = el.shadowRoot.querySelector('h1');
expect(heading.textContent).toBe(lblPotentialDuplicatesFoundOne);
const duplicateLink = el.shadowRoot.querySelector('[data-qa-locator="viewDuplicatesURL"] a');
expect(duplicateLink).not.toBeNull();
expect(duplicateLink.textContent).toBe(lblViewDuplicates);
});

it('text changes for multiple duplicates', async () => {
getDuplicates.mockResolvedValue({"setOfMatches" : "anotherId,moreIds,lastId"});
const el = createComponentForTest();
await flushPromises();

expect(getDuplicates).toHaveBeenCalled();
const heading = el.shadowRoot.querySelector('h1');
expect(heading.textContent).toBe(lblPotentialDuplicatesFoundMultiple);
const duplicateLink = el.shadowRoot.querySelector('[data-qa-locator="viewDuplicatesURL"] a');
expect(duplicateLink).not.toBeNull();
expect(duplicateLink.textContent).toBe(lblViewDuplicates);
});

it('renders an error when callout fails', async () => {
getDuplicates.mockRejectedValue({"status" : "fail", "body" : {"message" : "test"}});
const el = createComponentForTest();
await flushPromises();

const heading = el.shadowRoot.querySelector('h1');
expect(heading.textContent).toBe(lblPotentialDuplicatesFoundNone);
const error = el.shadowRoot.querySelector('[data-qa-locator="error"]');
expect(error).not.toBeNull();
});
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.wrapped-content{
text-wrap: wrap;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<template>
<template lwc:if={displayCard}>
<lightning-card variant="narrow" icon-name="standard:merge">
<h1 slot="title" class="wrapped-content">{lblTitle}</h1>
<p class="slds-var-m-left_xx-large" lwc:if={viewDuplicatesURL} data-qa-locator="viewDuplicatesURL">
<a class="slds-var-m-left_xx-small" onclick={navigateToContactMerge}>{lblViewDuplicatesLink}</a>
</p>
<p class="slds-var-m-left_xx-large" lwc:if={error} data-qa-locator="error">
<span class="slds-text-color_error">{error}</span>
</p>
</lightning-card>
</template>
</template>
103 changes: 103 additions & 0 deletions force-app/main/default/lwc/potentialDuplicates/potentialDuplicates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { LightningElement, api, track } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
import { getCurrentNamespace, showToast } from 'c/utilCommon';

import lblPotentialDuplicatesFoundNone from '@salesforce/label/c.potentialDuplicatesFoundNone';
import lblPotentialDuplicatesFoundOne from '@salesforce/label/c.potentialDuplicatesFoundOne';
import lblPotentialDuplicatesFoundMultiple from '@salesforce/label/c.potentialDuplicatesFoundMultiple';
import lblViewDuplicates from '@salesforce/label/c.viewDuplicates';

import getDuplicates from '@salesforce/apex/PotentialDuplicates.getDuplicates';

export default class PotentialDuplicates extends NavigationMixin(LightningElement) {

@api recordId;
@api displayCard;
@api displayToast;

@track isEnabled = true;
@track duplicateCount;
@track error;
lblTitle = lblPotentialDuplicatesFoundNone;
lblViewDuplicatesLink = lblViewDuplicates;
viewDuplicatesURL;

connectedCallback() {
if (this.recordId) {
getDuplicates({ recordId: this.recordId })
.then(response => {
this.handleDuplicates(response);
this.error = null;
})
.catch(error => {
this.error = this.handleError(error);
});
}
}

handleDuplicates(response) {
this.duplicateCount = 0;
if (response && response.setOfMatches) {
this.duplicateIdsParam = this.recordId + ',' + response.setOfMatches;
this.duplicateCount = response.setOfMatches.split(',').length;
}
this.generateDuplicatesURL();
this.updateTitle();
this.handleToast();
}

handleError(error) {
if (error && error.status && error.body) {
return error.body.message;
} else if (error && error.name && error.message) {
return error.message;
}
return "";
}

handleToast() {
if (this.displayToast && this.duplicateCount > 0) {
let messageData = [{
"url": this.viewDuplicatesURL,
"label": this.lblViewDuplicatesLink,
}];
showToast("", this.lblTitle + " {0}", "info", "sticky", messageData);
}
}

updateTitle() {
switch (this.duplicateCount) {
case 0:
this.lblTitle = lblPotentialDuplicatesFoundNone;
break;
case 1:
this.lblTitle = lblPotentialDuplicatesFoundOne;
break;
default:
this.lblTitle = lblPotentialDuplicatesFoundMultiple.replace("{0}", this.duplicateCount.toString());
}
}

generateDuplicatesURL() {
if (this.duplicateCount > 0) {
const contactMerge = 'CON_ContactMerge';
const namespace = getCurrentNamespace();
const contactMergePage = namespace ? `${namespace}__${contactMerge}` : contactMerge;
this.viewDuplicatesURL = "/apex/" + contactMergePage + "?searchIds=" + this.duplicateIdsParam;
}
else {
this.viewDuplicatesURL = "";
}
}

navigateToContactMerge() {
this[NavigationMixin.GenerateUrl]({
type: "standard__webPage",
attributes: {
url: this.viewDuplicatesURL
}
}).then(generatedUrl => {
window.location.assign(generatedUrl);
});
}
}
Loading

0 comments on commit 01ce129

Please sign in to comment.