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

New LWC-based Config Management #1436

Merged
merged 64 commits into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from 63 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
6eeadae
init rollupEditor LWC
JimBTek Mar 29, 2023
0a2d244
basic structure and initial sections
JimBTek Mar 29, 2023
176188f
Status check lwc and apex controller
sfenton3 Mar 30, 2023
19806bc
Add container application and associated MDT
aheber Mar 30, 2023
6c31b4d
Switch to DataTable for Rollups
aheber Mar 30, 2023
541d573
Update editor layout with Health check lwc
sfenton3 Mar 30, 2023
f73469d
integrate rollups and status check
JimBTek Mar 30, 2023
4fc3abe
update RollupStatusCheck component UI
lynguyenkhang Apr 4, 2023
0cee4d0
Create objectSelector.js
AishwaryaBadri Apr 6, 2023
17fbec4
Add files via upload
AishwaryaBadri Apr 6, 2023
11b0728
Add files via upload
AishwaryaBadri Apr 6, 2023
4048175
pull it together and refactor code locations
aheber Apr 7, 2023
4544f6f
Add validation and (de)activation
aheber Apr 9, 2023
4d2d965
feature: the MVP UI of RollupEditor component
lynguyenkhang Apr 12, 2023
c047b37
fix: improve the layout of RollupEditor component
lynguyenkhang Apr 13, 2023
2cfa0c5
feature: autocomplete combobox
lynguyenkhang Apr 16, 2023
6ca1d50
chore: rename method in RollupEditorController
lynguyenkhang Apr 17, 2023
89ac288
fix: "value" property setter
lynguyenkhang Apr 17, 2023
c7d7820
chore: Prettier reformatting
lynguyenkhang Apr 19, 2023
7c77386
Improve Object Picker behaviors and add features
aheber Apr 19, 2023
5e9d855
feature: UI separations between Edit and Create mode
lynguyenkhang May 1, 2023
32962b5
feature: rollup name as Header
lynguyenkhang May 1, 2023
313f69b
feature: refresh FieldOptions everywhen selected objects are changed
lynguyenkhang May 1, 2023
520fdb9
updated v58.0 to v59.0, fixes #1396
groundwired Oct 31, 2023
eca4314
Merge in improvements from ah branch
aheber Jan 10, 2024
140d134
Restore base components to editor
aheber Jan 10, 2024
8147cad
Cleanup rollup check and integrate
aheber Jan 12, 2024
f2bacee
WIP changing layout, adding path
aheber Jan 21, 2024
c73a370
Add vf links
aheber Jan 23, 2024
3e6d68b
Fix menu alignment
aheber Jan 23, 2024
f359c36
Move editor to modal
aheber Jan 30, 2024
89c190a
improve modal and path
aheber Feb 8, 2024
c09ca17
Merge branch 'main' into feature/nw/rollup-lwc
aheber Feb 13, 2024
9635df6
Remove onmouseleave behavior from autocomplete combobox
octatau Feb 13, 2024
48d6120
Add property to disable autocomplete combobox
octatau Feb 14, 2024
fe3176d
Disable field selection comboboxes in rollup editor when no options a…
octatau Feb 14, 2024
1b2fda6
Add ability to specify a search threshold on the autocomplete combobox
octatau Feb 14, 2024
184fd89
Use levenshtein distance to sort search results in autocomplete combobox
octatau Feb 14, 2024
9ae4cf2
Update selectOptionByValue function to identify options based on exac…
octatau Feb 14, 2024
9932e63
Merge branch 'SFDO-Community:feature/nw/rollup-lwc' into feature/nw/r…
octatau Feb 14, 2024
49719cf
Add autocomplete combobox property to indicate that search is require…
octatau Feb 15, 2024
b36d8fc
Add autocomplete combobox property to indicate the maximum number of …
octatau Feb 15, 2024
75a0226
Trigger autocomplete combobox on focus to fix unexpected double click…
octatau Feb 15, 2024
227d80b
Display configurable message when no autocomplete options are available
octatau Feb 15, 2024
a4599e7
Utilize autocomplete combobox for object selector
octatau Feb 15, 2024
def69ff
Decouple show/hide behavior in autocomplete combobox to manage visibi…
octatau Feb 15, 2024
6a79198
Merge pull request #1432 from octatau/feature/nw/rollup-lwc
aheber Feb 15, 2024
771b0a8
implement UI feedback
aheber Feb 15, 2024
1409243
Improve column labels
aheber Feb 15, 2024
09d76a3
Take default modal size
aheber Feb 15, 2024
cb98c1c
Add rough ability to manage the scheduled jobs
aheber Feb 25, 2024
eacfa93
Remove js-lenvenshtein lib from objectSelector
octatau Mar 1, 2024
e703949
Remove unused propoerties from object selector
octatau Mar 1, 2024
add470f
Render icons immediately after they are available
octatau Mar 1, 2024
57a12f5
Merge pull request #1435 from octatau/feature/nw/rollup-lwc
aheber Mar 1, 2024
67ec5ac
Update job scheduler
aheber Mar 3, 2024
0388e02
Update schedule style and details
aheber Mar 3, 2024
e5acaf9
Improve schedule UI
aheber Mar 13, 2024
374fa75
Relabel calculation mode in manage
aheber Mar 17, 2024
ad1b474
Improve path and cleanup status check
aheber Mar 17, 2024
ced8622
keep path updated
aheber Mar 17, 2024
dd1be93
Smooth out object selection
aheber Mar 17, 2024
e77d6fa
sort object fields
aheber Mar 19, 2024
bac757b
Make tab navigation support namespaced orgs
aheber Mar 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions dlrs/main/classes/AsyncApexJobsSelector.cls
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class AsyncApexJobsSelector extends fflib_SObjectSelector {
AsyncApexJob.Status,
AsyncApexJob.ExtendedStatus,
AsyncApexJob.CreatedDate,
AsyncApexJob.CronTriggerId,
AsyncApexJob.CompletedDate,
AsyncApexJob.JobItemsProcessed,
AsyncApexJob.TotalJobItems,
Expand Down Expand Up @@ -63,11 +64,59 @@ public class AsyncApexJobsSelector extends fflib_SObjectSelector {
String query = newQueryFactory()
.setCondition(
'JobType = :jobType And ' +
'ApexClass.Name in :classNames And ' +
'Status in :statuses'
'ApexClass.Name in :classNames And ' +
'Status in :statuses'
)
.toSOQL();
List<AsyncApexJob> jobs = (List<AsyncApexJob>) Database.query(query);
return jobs.size() > 0;
}

/**
* Get all scheduled instances of a class
**/
public List<AsyncApexJob> getScheduledInstancesOfType(Type classType) {
String classFullyQualifiedName = classType.getName();
List<String> nameParts = classFullyQualifiedName.split('\\.');

// Probably doesn't work for inner-classes in this format
String namespace;
if (nameParts.size() > 1) {
namespace = nameParts.remove(0);
}
String className = nameParts.remove(0);

String query = newQueryFactory()
.selectFields(
new List<String>{
'ApexClass.Name',
'CronTrigger.CronJobDetail.Name',
'CronTrigger.CronExpression',
'CronTrigger.NextFireTime'
}
)
.setCondition(
'Status != \'Aborted\' AND ' +
'JobType = \'ScheduledApex\' AND ' +
'ApexClass.Name = :className AND ' +
'ApexClass.NamespacePrefix = :namespace'
)
.addOrdering(
'CronTrigger.NextFireTime',
fflib_QueryFactory.SortOrder.ASCENDING
)
.toSOQL();
return Database.query(query);
}

/**
* Get all scheduled instances of a class
**/
public List<AsyncApexJob> getAllScheduledJobs() {
String query = newQueryFactory()
.selectField('CronTrigger.CronJobDetail.Name')
.setCondition('JobType = \'ScheduledApex\' AND Status != \'Aborted\'')
.toSOQL();
return Database.query(query);
}
}
155 changes: 133 additions & 22 deletions dlrs/main/classes/CustomMetadataService.cls
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,46 @@ public class CustomMetadataService {
) {
}

/**
* Starts async metadata save using Apex Metadata API
*/
public static Id initiateMetadataSave(List<SObject> records) {
Metadata.DeployContainer mdContainer = new Metadata.DeployContainer();
for (SObject r : records) {
// Setup custom metadata to be created in the subscriber org.
Metadata.CustomMetadata customMetadata = new Metadata.CustomMetadata();
// Developer name and Label are applied here
customMetadata.fullName =
r.getSObjectType().getDescribe().getName() +
'.' +
(String) r.get('DeveloperName');
customMetadata.label = (String) r.get('Label');
Metadata.CustomMetadata mdt = new Metadata.CustomMetadata();
Map<String, Object> populatedFields = r.getPopulatedFieldsAsMap();
// We don't want these in values
List<String> ignoredKeys = new List<String>{
'Id',
'DeveloperName',
'Label'
};
for (String key : populatedFields.keySet()) {
if (ignoredKeys.contains(key)) {
continue;
}
Metadata.CustomMetadataValue customField = new Metadata.CustomMetadataValue();
customField.field = key;
customField.value = populatedFields.get(key);
customMetadata.values.add(customField);
}

mdContainer.addMetadata(customMetadata);
}

ApexMdApiDeployCallback cb = new ApexMdApiDeployCallback();
// Enqueue custom metadata deployment
return Metadata.Operations.enqueueDeployment(mdContainer, cb);
}

/**
* Insert the given Custom Metadata records into the orgs config
**/
Expand Down Expand Up @@ -75,8 +115,8 @@ public class CustomMetadataService {
for (String customMetadataFullName : customMetadataFullNames)
qualifiedFullNames.add(
qualifiedMetadataType.getDescribe().getName() +
'.' +
customMetadataFullName
'.' +
customMetadataFullName
);
List<MetadataService.DeleteResult> results = service.deleteMetadata(
'CustomMetadata',
Expand All @@ -85,6 +125,21 @@ public class CustomMetadataService {
handleDeleteResults(results[0]);
}

/**
* Delete the given Custom Metadata records from the org using Async action
**/
public static Id deleteMetadataAsync(
SObjectType qualifiedMetadataType,
List<String> customMetadataFullNames
) {
return System.enqueueJob(
new DeleteMetadataQueueable(
qualifiedMetadataType,
customMetadataFullNames
)
);
}

public class CustomMetadataServiceException extends Exception {
}

Expand Down Expand Up @@ -156,20 +211,20 @@ public class CustomMetadataService {
List<String> messages = new List<String>();
messages.add(
(saveResult.errors.size() == 1 ? 'Error ' : 'Errors ') +
'occured processing component ' +
saveResult.fullName +
'.'
'occured processing component ' +
saveResult.fullName +
'.'
);
for (MetadataService.Error error : saveResult.errors)
messages.add(
error.message +
' (' +
error.statusCode +
').' +
(error.fields != null &&
error.fields.size() > 0
? ' Fields ' + String.join(error.fields, ',') + '.'
: '')
' (' +
error.statusCode +
').' +
(error.fields != null &&
error.fields.size() > 0
? ' Fields ' + String.join(error.fields, ',') + '.'
: '')
);
if (messages.size() > 0)
throw new CustomMetadataServiceException(String.join(messages, ' '));
Expand All @@ -194,20 +249,20 @@ public class CustomMetadataService {
List<String> messages = new List<String>();
messages.add(
(deleteResult.errors.size() == 1 ? 'Error ' : 'Errors ') +
'occured processing component ' +
deleteResult.fullName +
'.'
'occured processing component ' +
deleteResult.fullName +
'.'
);
for (MetadataService.Error error : deleteResult.errors)
messages.add(
error.message +
' (' +
error.statusCode +
').' +
(error.fields != null &&
error.fields.size() > 0
? ' Fields ' + String.join(error.fields, ',') + '.'
: '')
' (' +
error.statusCode +
').' +
(error.fields != null &&
error.fields.size() > 0
? ' Fields ' + String.join(error.fields, ',') + '.'
: '')
);
if (messages.size() > 0)
throw new CustomMetadataServiceException(String.join(messages, ' '));
Expand All @@ -217,4 +272,60 @@ public class CustomMetadataService {
'Request failed with no specified error.'
);
}

class ApexMdApiDeployCallback implements Metadata.DeployCallback {
public void handleResult(
Metadata.DeployResult result,
Metadata.DeployCallbackContext context
) {
UserNotification__e updateEvent = new UserNotification__e(
Type__c = 'DeploymentResult',
Payload__c = JSON.serialize(result)
);
EventBus.publish(updateEvent);
}
}

class DeleteMetadataQueueable implements Queueable, Database.AllowsCallouts {
SObjectType qualifiedMetadataType;
List<String> customMetadataFullNames;
public DeleteMetadataQueueable(
SObjectType qualifiedMetadataType,
List<String> customMetadataFullNames
) {
this.qualifiedMetadataType = qualifiedMetadataType;
this.customMetadataFullNames = customMetadataFullNames;
}

public void execute(QueueableContext ctx) {
try {
CustomMetadataService.deleteMetadata(
qualifiedMetadataType,
customMetadataFullNames
);
UserNotification__e updateEvent = new UserNotification__e(
Type__c = 'DeleteRequestResult',
Payload__c = JSON.serialize(
new Map<String, Object>{
'success' => true,
'metadataNames' => customMetadataFullNames
}
)
);
EventBus.publish(updateEvent);
} catch (CustomMetadataService.CustomMetadataServiceException e) {
UserNotification__e updateEvent = new UserNotification__e(
Type__c = 'DeleteRequestResult',
Payload__c = JSON.serialize(
new Map<String, Object>{
'success' => false,
'metadataNames' => customMetadataFullNames,
'error' => e.getMessage()
}
)
);
EventBus.publish(updateEvent);
}
}
}
}
79 changes: 79 additions & 0 deletions dlrs/main/classes/LookupRollupStatusCheckController.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
public with sharing class LookupRollupStatusCheckController {
/**
* Get count of scheduled items older than yesterday
* The assumption is that normal processing should have handled these
*/
@AuraEnabled(Cacheable=true)
public static Integer getOutstandingScheduledItemsForLookup(String lookupID) {
return [
SELECT COUNT()
FROM LookupRollupSummaryScheduleItems__c
WHERE LookupRollupSummary2__c = :lookupID AND LastModifiedDate < YESTERDAY
];
}

/**
* Check if the rollup has a Full Calculate schedule
*/
@AuraEnabled
public static Datetime getScheduledFullCalculates(String lookupID) {
try {
LookupRollupSummary2__mdt LookupRollupSummary = (LookupRollupSummary2__mdt) new RollupSummariesSelector.CustomMetadataSelector(
false,
true
)
.selectById(new Set<String>{ lookupID })[0]
.Record;

if (LookupRollupSummary != null) {
String id = (LookupRollupSummary.id).to15();
List<CronTrigger> ct = new CronTriggersSelector()
.selectScheduledApexById(id);

if (!ct.isEmpty()) {
return ct[0].NextFireTime;
}
}
} catch (Exception e) {
}
return null;
}

/**
* Check if the rollup has a child/parent trigger
*/
@AuraEnabled
public static Boolean hasChildTriggerDeployed(String lookupID) {
try {
LookupRollupSummary2__mdt LookupRollupSummary = (LookupRollupSummary2__mdt) new RollupSummariesSelector.CustomMetadataSelector(
false,
true
)
.selectById(new Set<String>{ lookupID })[0]
.Record;

if (LookupRollupSummary != null) {
RollupSummary rs = new RollupSummary(LookupRollupSummary);
String childTrigger = RollupSummaries.makeTriggerName(rs);
ApexTriggersSelector selector = new ApexTriggersSelector();
Map<String, ApexTrigger> loadTriggers = selector.selectByName(
new Set<String>{ ChildTrigger }
);

return loadTriggers.containsKey(ChildTrigger);
}
} catch (Exception e) {
}
return false;
}

/**
* Check if cron job is running for DLRS
*/
@AuraEnabled
public static Integer getScheduledJobs() {
return new AsyncApexJobsSelector()
.getScheduledInstancesOfType(RollupJob.class)
.size();
}
}
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>60.0</apiVersion>
<status>Active</status>
</ApexClass>
4 changes: 3 additions & 1 deletion dlrs/main/classes/ManageLookupRollupSummariesController.cls
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,9 @@ public with sharing class ManageLookupRollupSummariesController {

public PageReference newWizard() {
try {
PageReference newPage = Page.managelookuprollupsummaries_New;
PageReference newPage = new PageReference(
'/lightning/n/ManageLookupRollupSummaries2'
);
newPage.setRedirect(true);
return newPage;
} catch (Exception e) {
Expand Down
26 changes: 26 additions & 0 deletions dlrs/main/classes/ObjectSelectorController.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
public with sharing class ObjectSelectorController {
@AuraEnabled(cacheable=true)
public static List<SObjectInfo> getParentObjList() {
Map<String, Schema.SObjectType> objectDescription = Schema.getGlobalDescribe();
List<SObjectInfo> objects = new List<SObjectInfo>();
for (Schema.SObjectType obj : objectDescription.values()) {
objects.add(new SObjectInfo(obj));
}
return objects;
}

class SObjectInfo {
@AuraEnabled
public String fullName;
@AuraEnabled
public String label;

public SObjectInfo(Schema.SObjectType obj) {
Schema.DescribeSObjectResult description = obj.getDescribe(
SObjectDescribeOptions.DEFERRED
);
this.fullName = description.getName();
this.label = description.getLabel();
}
}
}
Loading
Loading