Repository: douglascayers-org/sfdx-mass-action-scheduler
Branch: develop
Commit: b07f7c893be7
Files: 329
Total size: 1.6 MB
Directory structure:
gitextract_80zxm3lv/
├── .forceignore
├── .github/
│ ├── CODEOWNERS
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── BUG_REPORT.md
│ │ ├── DOCUMENTATION_REQUEST.md
│ │ └── FEATURE_REQUEST.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── main.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── _config.yml
├── _layouts/
│ └── default.html
├── config/
│ └── project-scratch-def.json
├── force-app/
│ └── main/
│ └── default/
│ ├── applications/
│ │ ├── Mass_Action_Scheduler.app-meta.xml
│ │ └── Mass_Action_Scheduler_Lightning.app-meta.xml
│ ├── aura/
│ │ ├── LC_API/
│ │ │ ├── LC_API.cmp
│ │ │ ├── LC_API.cmp-meta.xml
│ │ │ ├── LC_APIController.js
│ │ │ ├── LC_APIHelper.js
│ │ │ └── LC_APIRenderer.js
│ │ ├── LC_URL/
│ │ │ ├── LC_URL.cmp
│ │ │ ├── LC_URL.cmp-meta.xml
│ │ │ └── LC_URLController.js
│ │ ├── MA_CheckForPackageUpdatesCmp/
│ │ │ ├── MA_CheckForPackageUpdatesCmp.cmp
│ │ │ ├── MA_CheckForPackageUpdatesCmp.cmp-meta.xml
│ │ │ └── MA_CheckForPackageUpdatesCmpController.js
│ │ ├── MA_DevelopedByCmp/
│ │ │ ├── MA_DevelopedByCmp.cmp
│ │ │ └── MA_DevelopedByCmp.cmp-meta.xml
│ │ ├── MA_EditConfigCmp/
│ │ │ ├── MA_EditConfigCmp.cmp
│ │ │ ├── MA_EditConfigCmp.cmp-meta.xml
│ │ │ ├── MA_EditConfigCmp.css
│ │ │ ├── MA_EditConfigCmpController.js
│ │ │ └── MA_EditConfigCmpHelper.js
│ │ ├── MA_FlowStagePathCmp/
│ │ │ ├── MA_FlowStagePathCmp.cmp
│ │ │ ├── MA_FlowStagePathCmp.cmp-meta.xml
│ │ │ ├── MA_FlowStagePathCmp.design
│ │ │ └── MA_FlowStagePathCmpController.js
│ │ ├── MA_RunConfigCmp/
│ │ │ ├── MA_RunConfigCmp.cmp
│ │ │ ├── MA_RunConfigCmp.cmp-meta.xml
│ │ │ ├── MA_RunConfigCmpController.js
│ │ │ └── MA_RunConfigCmpHelper.js
│ │ ├── MA_WizardCoachingCmp/
│ │ │ ├── MA_WizardCoachingCmp.cmp
│ │ │ └── MA_WizardCoachingCmp.cmp-meta.xml
│ │ ├── defaultTokens/
│ │ │ ├── defaultTokens.tokens
│ │ │ └── defaultTokens.tokens-meta.xml
│ │ ├── lax/
│ │ │ ├── lax.cmp
│ │ │ ├── lax.cmp-meta.xml
│ │ │ ├── laxController.js
│ │ │ └── laxHelper.js
│ │ ├── slds_label/
│ │ │ ├── slds_label.cmp
│ │ │ └── slds_label.cmp-meta.xml
│ │ ├── slds_section/
│ │ │ ├── slds_section.cmp
│ │ │ └── slds_section.cmp-meta.xml
│ │ ├── strike_evt/
│ │ │ ├── strike_evt.evt
│ │ │ └── strike_evt.evt-meta.xml
│ │ └── strike_wizard/
│ │ ├── strike_wizard.cmp
│ │ ├── strike_wizard.cmp-meta.xml
│ │ ├── strike_wizard.css
│ │ ├── strike_wizardController.js
│ │ └── strike_wizardHelper.js
│ ├── classes/
│ │ ├── LC_URLController.cls
│ │ ├── LC_URLController.cls-meta.xml
│ │ ├── LC_URLControllerTest.cls
│ │ ├── LC_URLControllerTest.cls-meta.xml
│ │ ├── LC_VisualforceDomainController.cls
│ │ ├── LC_VisualforceDomainController.cls-meta.xml
│ │ ├── LC_VisualforceDomainControllerTest.cls
│ │ ├── LC_VisualforceDomainControllerTest.cls-meta.xml
│ │ ├── MA_AnonymousApexExecuteResult.cls
│ │ ├── MA_AnonymousApexExecuteResult.cls-meta.xml
│ │ ├── MA_AsyncApexJobMock.cls
│ │ ├── MA_AsyncApexJobMock.cls-meta.xml
│ │ ├── MA_BatchApexErrorEventInvocable.cls
│ │ ├── MA_BatchApexErrorEventInvocable.cls-meta.xml
│ │ ├── MA_BatchApexErrorEventInvocableTest.cls
│ │ ├── MA_BatchApexErrorEventInvocableTest.cls-meta.xml
│ │ ├── MA_BatchApexStatusEventTriggerHandler.cls
│ │ ├── MA_BatchApexStatusEventTriggerHandler.cls-meta.xml
│ │ ├── MA_BatchApexStatusEventTriggerTest.cls
│ │ ├── MA_BatchApexStatusEventTriggerTest.cls-meta.xml
│ │ ├── MA_EditConfigCmpController.cls
│ │ ├── MA_EditConfigCmpController.cls-meta.xml
│ │ ├── MA_EditConfigCmpControllerTest.cls
│ │ ├── MA_EditConfigCmpControllerTest.cls-meta.xml
│ │ ├── MA_EditConfigRestController.cls
│ │ ├── MA_EditConfigRestController.cls-meta.xml
│ │ ├── MA_EditConfigRestControllerTest.cls
│ │ ├── MA_EditConfigRestControllerTest.cls-meta.xml
│ │ ├── MA_Exceptions.cls
│ │ ├── MA_Exceptions.cls-meta.xml
│ │ ├── MA_ExceptionsTest.cls
│ │ ├── MA_ExceptionsTest.cls-meta.xml
│ │ ├── MA_HttpCalloutMock.cls
│ │ ├── MA_HttpCalloutMock.cls-meta.xml
│ │ ├── MA_InstallHandler.cls
│ │ ├── MA_InstallHandler.cls-meta.xml
│ │ ├── MA_InstallHandlerTest.cls
│ │ ├── MA_InstallHandlerTest.cls-meta.xml
│ │ ├── MA_IterableSourceBatchable.cls
│ │ ├── MA_IterableSourceBatchable.cls-meta.xml
│ │ ├── MA_IterableSourceBatchableTest.cls
│ │ ├── MA_IterableSourceBatchableTest.cls-meta.xml
│ │ ├── MA_JobChangeEvent.cls
│ │ ├── MA_JobChangeEvent.cls-meta.xml
│ │ ├── MA_JobChangeEventTriggerHandler.cls
│ │ ├── MA_JobChangeEventTriggerHandler.cls-meta.xml
│ │ ├── MA_JobChangeEventTriggerHandlerTest.cls
│ │ ├── MA_JobChangeEventTriggerHandlerTest.cls-meta.xml
│ │ ├── MA_ListViewDescribeResult.cls
│ │ ├── MA_ListViewDescribeResult.cls-meta.xml
│ │ ├── MA_ListViewDescribeResultTest.cls
│ │ ├── MA_ListViewDescribeResultTest.cls-meta.xml
│ │ ├── MA_ListViewSourceBatchable.cls
│ │ ├── MA_ListViewSourceBatchable.cls-meta.xml
│ │ ├── MA_ListViewSourceBatchableTest.cls
│ │ ├── MA_ListViewSourceBatchableTest.cls-meta.xml
│ │ ├── MA_MapUtils.cls
│ │ ├── MA_MapUtils.cls-meta.xml
│ │ ├── MA_MapUtilsTest.cls
│ │ ├── MA_MapUtilsTest.cls-meta.xml
│ │ ├── MA_MassActionBatchEnqueuer.cls
│ │ ├── MA_MassActionBatchEnqueuer.cls-meta.xml
│ │ ├── MA_MassActionBatchUtils.cls
│ │ ├── MA_MassActionBatchUtils.cls-meta.xml
│ │ ├── MA_MassActionBatchUtilsTest.cls
│ │ ├── MA_MassActionBatchUtilsTest.cls-meta.xml
│ │ ├── MA_MassActionConfigTriggerHandler.cls
│ │ ├── MA_MassActionConfigTriggerHandler.cls-meta.xml
│ │ ├── MA_MassActionConfigTriggerHandlerTest.cls
│ │ ├── MA_MassActionConfigTriggerHandlerTest.cls-meta.xml
│ │ ├── MA_MassActionConfigWrapper.cls
│ │ ├── MA_MassActionConfigWrapper.cls-meta.xml
│ │ ├── MA_MassActionLogTriggerHandler.cls
│ │ ├── MA_MassActionLogTriggerHandler.cls-meta.xml
│ │ ├── MA_MassActionLogTriggerHandlerTest.cls
│ │ ├── MA_MassActionLogTriggerHandlerTest.cls-meta.xml
│ │ ├── MA_MassActionSchedulable.cls
│ │ ├── MA_MassActionSchedulable.cls-meta.xml
│ │ ├── MA_MassActionSchedulableTest.cls
│ │ ├── MA_MassActionSchedulableTest.cls-meta.xml
│ │ ├── MA_MassActionScheduleUtils.cls
│ │ ├── MA_MassActionScheduleUtils.cls-meta.xml
│ │ ├── MA_MassActionScheduleUtilsTest.cls
│ │ ├── MA_MassActionScheduleUtilsTest.cls-meta.xml
│ │ ├── MA_MassActionUtils.cls
│ │ ├── MA_MassActionUtils.cls-meta.xml
│ │ ├── MA_MetadataDeployCallback.cls
│ │ ├── MA_MetadataDeployCallback.cls-meta.xml
│ │ ├── MA_MetadataDeployCallbackTest.cls
│ │ ├── MA_MetadataDeployCallbackTest.cls-meta.xml
│ │ ├── MA_NamespaceUtils.cls
│ │ ├── MA_NamespaceUtils.cls-meta.xml
│ │ ├── MA_ReportService.cls
│ │ ├── MA_ReportService.cls-meta.xml
│ │ ├── MA_ReportServiceTest.cls
│ │ ├── MA_ReportServiceTest.cls-meta.xml
│ │ ├── MA_ReportSourceBatchable.cls
│ │ ├── MA_ReportSourceBatchable.cls-meta.xml
│ │ ├── MA_ReportSourceBatchableTest.cls
│ │ ├── MA_ReportSourceBatchableTest.cls-meta.xml
│ │ ├── MA_RunConfigCmpController.cls
│ │ ├── MA_RunConfigCmpController.cls-meta.xml
│ │ ├── MA_RunConfigCmpControllerTest.cls
│ │ ├── MA_RunConfigCmpControllerTest.cls-meta.xml
│ │ ├── MA_RunConfigInvocable.cls
│ │ ├── MA_RunConfigInvocable.cls-meta.xml
│ │ ├── MA_RunConfigInvocableTest.cls
│ │ ├── MA_RunConfigInvocableTest.cls-meta.xml
│ │ ├── MA_SetConfigUniqueNameBatchable.cls
│ │ ├── MA_SetConfigUniqueNameBatchable.cls-meta.xml
│ │ ├── MA_SetConfigUniqueNameBatchableTest.cls
│ │ ├── MA_SetConfigUniqueNameBatchableTest.cls-meta.xml
│ │ ├── MA_SetupAuthWizardPageController.cls
│ │ ├── MA_SetupAuthWizardPageController.cls-meta.xml
│ │ ├── MA_SetupAuthWizardPageControllerTest.cls
│ │ ├── MA_SetupAuthWizardPageControllerTest.cls-meta.xml
│ │ ├── MA_SoqlQueryExecuteResult.cls
│ │ ├── MA_SoqlQueryExecuteResult.cls-meta.xml
│ │ ├── MA_SoqlQueryExecuteResultTest.cls
│ │ ├── MA_SoqlQueryExecuteResultTest.cls-meta.xml
│ │ ├── MA_SoqlSourceBatchable.cls
│ │ ├── MA_SoqlSourceBatchable.cls-meta.xml
│ │ ├── MA_SoqlSourceBatchableTest.cls
│ │ ├── MA_SoqlSourceBatchableTest.cls-meta.xml
│ │ ├── MA_SoqlSourceIterable.cls
│ │ ├── MA_SoqlSourceIterable.cls-meta.xml
│ │ ├── MA_SoqlSourceIterableTest.cls
│ │ ├── MA_SoqlSourceIterableTest.cls-meta.xml
│ │ ├── MA_StringUtils.cls
│ │ ├── MA_StringUtils.cls-meta.xml
│ │ ├── MA_StringUtilsTest.cls
│ │ ├── MA_StringUtilsTest.cls-meta.xml
│ │ ├── MA_UpgradeMassActionLogsBatchable.cls
│ │ ├── MA_UpgradeMassActionLogsBatchable.cls-meta.xml
│ │ ├── MA_UpgradeMassActionLogsBatchableTest.cls
│ │ ├── MA_UpgradeMassActionLogsBatchableTest.cls-meta.xml
│ │ ├── MA_UpgradePageLayoutsService.cls
│ │ ├── MA_UpgradePageLayoutsService.cls-meta.xml
│ │ ├── MA_UpgradePageLayoutsServiceTest.cls
│ │ ├── MA_UpgradePageLayoutsServiceTest.cls-meta.xml
│ │ ├── MA_XMLUtils.cls
│ │ ├── MA_XMLUtils.cls-meta.xml
│ │ ├── MA_XMLUtilsTest.cls
│ │ └── MA_XMLUtilsTest.cls-meta.xml
│ ├── contentassets/
│ │ ├── maslogominimal.asset
│ │ └── maslogominimal.asset-meta.xml
│ ├── flexipages/
│ │ ├── Mass_Action_Configuration_Record_Page.flexipage-meta.xml
│ │ ├── Mass_Action_Configuration_Record_Page_One_Column.flexipage-meta.xml
│ │ └── Mass_Action_Log_Record_Page.flexipage-meta.xml
│ ├── flows/
│ │ ├── MAS_Batch_Apex_Error_Event.flow-meta.xml
│ │ ├── MAS_Mass_Action_Configuration.flow-meta.xml
│ │ └── MAS_Run_Mass_Action_Flow.flow-meta.xml
│ ├── layouts/
│ │ ├── Mass_Action_Configuration__c-Mass Action Configuration Layout.layout-meta.xml
│ │ ├── Mass_Action_Log__c-Mass Action Child Log Layout.layout-meta.xml
│ │ ├── Mass_Action_Log__c-Mass Action Log Layout.layout-meta.xml
│ │ ├── Mass_Action_Log__c-Mass Action Parent Log Layout.layout-meta.xml
│ │ └── Mass_Action_Mapping__c-Mass Action Mapping Layout.layout-meta.xml
│ ├── namedCredentials/
│ │ └── Mass_Action_Test_Named_Credential.namedCredential-meta.xml
│ ├── notificationtypes/
│ │ └── Mass_Action_Notification.notiftype-meta.xml
│ ├── objects/
│ │ ├── Mass_Action_Batch_Apex_Status_Event__e/
│ │ │ ├── Mass_Action_Batch_Apex_Status_Event__e.object-meta.xml
│ │ │ └── fields/
│ │ │ ├── Job_ID__c.field-meta.xml
│ │ │ ├── Job_Scope__c.field-meta.xml
│ │ │ ├── Long_Message__c.field-meta.xml
│ │ │ ├── Message_Type__c.field-meta.xml
│ │ │ ├── Message__c.field-meta.xml
│ │ │ ├── Phase__c.field-meta.xml
│ │ │ └── Timestamp__c.field-meta.xml
│ │ ├── Mass_Action_Configuration__c/
│ │ │ ├── Mass_Action_Configuration__c.object-meta.xml
│ │ │ ├── compactLayouts/
│ │ │ │ └── Default_Compact_Layout.compactLayout-meta.xml
│ │ │ ├── fields/
│ │ │ │ ├── Active__c.field-meta.xml
│ │ │ │ ├── Batch_Size__c.field-meta.xml
│ │ │ │ ├── Description__c.field-meta.xml
│ │ │ │ ├── DeveloperName__c.field-meta.xml
│ │ │ │ ├── Last_Run_Completed_Date__c.field-meta.xml
│ │ │ │ ├── Last_Run_Completed_With_Errors__c.field-meta.xml
│ │ │ │ ├── Named_Credential__c.field-meta.xml
│ │ │ │ ├── Schedule_Cron__c.field-meta.xml
│ │ │ │ ├── Schedule_DayOfMonth__c.field-meta.xml
│ │ │ │ ├── Schedule_DayOfWeek__c.field-meta.xml
│ │ │ │ ├── Schedule_Frequency__c.field-meta.xml
│ │ │ │ ├── Schedule_HourOfDay__c.field-meta.xml
│ │ │ │ ├── Schedule_MinuteOfHour__c.field-meta.xml
│ │ │ │ ├── Schedule_MonthOfYear__c.field-meta.xml
│ │ │ │ ├── Schedule_SecondOfMinute__c.field-meta.xml
│ │ │ │ ├── Source_Apex_Class__c.field-meta.xml
│ │ │ │ ├── Source_List_View_ID__c.field-meta.xml
│ │ │ │ ├── Source_Report_Column_Name__c.field-meta.xml
│ │ │ │ ├── Source_Report_ID__c.field-meta.xml
│ │ │ │ ├── Source_SOQL_Query__c.field-meta.xml
│ │ │ │ ├── Source_Type__c.field-meta.xml
│ │ │ │ ├── Target_Action_Name__c.field-meta.xml
│ │ │ │ ├── Target_Apex_Script__c.field-meta.xml
│ │ │ │ ├── Target_SObject_Type__c.field-meta.xml
│ │ │ │ └── Target_Type__c.field-meta.xml
│ │ │ └── listViews/
│ │ │ └── All.listView-meta.xml
│ │ ├── Mass_Action_Job_Change_Event__e/
│ │ │ ├── Mass_Action_Job_Change_Event__e.object-meta.xml
│ │ │ └── fields/
│ │ │ └── Payload__c.field-meta.xml
│ │ ├── Mass_Action_Log__c/
│ │ │ ├── Mass_Action_Log__c.object-meta.xml
│ │ │ ├── compactLayouts/
│ │ │ │ ├── Child_Log_Compact_Layout.compactLayout-meta.xml
│ │ │ │ ├── Default_Compact_Layout.compactLayout-meta.xml
│ │ │ │ └── Parent_Log_Compact_Layout.compactLayout-meta.xml
│ │ │ ├── fields/
│ │ │ │ ├── Batch_Success_Percentage__c.field-meta.xml
│ │ │ │ ├── Batch_Success_Rate__c.field-meta.xml
│ │ │ │ ├── Failed_Batches__c.field-meta.xml
│ │ │ │ ├── Job_ID__c.field-meta.xml
│ │ │ │ ├── Job_Scope__c.field-meta.xml
│ │ │ │ ├── Long_Message__c.field-meta.xml
│ │ │ │ ├── Mass_Action_Configuration__c.field-meta.xml
│ │ │ │ ├── Message_Type__c.field-meta.xml
│ │ │ │ ├── Message__c.field-meta.xml
│ │ │ │ ├── Parent_Log_Configuration__c.field-meta.xml
│ │ │ │ ├── Parent_Log__c.field-meta.xml
│ │ │ │ ├── Processed_Batches__c.field-meta.xml
│ │ │ │ ├── Submitted_Date__c.field-meta.xml
│ │ │ │ ├── Timestamp__c.field-meta.xml
│ │ │ │ └── Total_Batches__c.field-meta.xml
│ │ │ ├── listViews/
│ │ │ │ ├── All.listView-meta.xml
│ │ │ │ └── All_Parent_Logs.listView-meta.xml
│ │ │ └── recordTypes/
│ │ │ ├── Child_Log.recordType-meta.xml
│ │ │ └── Parent_Log.recordType-meta.xml
│ │ └── Mass_Action_Mapping__c/
│ │ ├── Mass_Action_Mapping__c.object-meta.xml
│ │ ├── compactLayouts/
│ │ │ └── Default_Compact_Layout.compactLayout-meta.xml
│ │ └── fields/
│ │ ├── Mass_Action_Configuration__c.field-meta.xml
│ │ ├── Source_Field_Name__c.field-meta.xml
│ │ └── Target_Field_Name__c.field-meta.xml
│ ├── pages/
│ │ ├── LC_APIPage.page
│ │ ├── LC_APIPage.page-meta.xml
│ │ ├── LC_VisualforceDomainPage.page
│ │ ├── LC_VisualforceDomainPage.page-meta.xml
│ │ ├── MA_SetupAuthWizardPage.page
│ │ └── MA_SetupAuthWizardPage.page-meta.xml
│ ├── permissionsets/
│ │ └── Mass_Action_Admin.permissionset-meta.xml
│ ├── quickActions/
│ │ ├── Mass_Action_Configuration__c.Quick_Edit.quickAction-meta.xml
│ │ ├── Mass_Action_Configuration__c.Run.quickAction-meta.xml
│ │ └── Mass_Action_Configuration__c.Run_via_Flow.quickAction-meta.xml
│ ├── reports/
│ │ ├── Mass_Action_Test_Reports/
│ │ │ └── MA_Test_Account_Report.report-meta.xml
│ │ └── Mass_Action_Test_Reports.reportFolder-meta.xml
│ ├── staticresources/
│ │ ├── MA_SalesforceSecurityLogo.resource-meta.xml
│ │ ├── jquery.js
│ │ ├── jquery.resource-meta.xml
│ │ ├── jsforce.js
│ │ ├── jsforce.resource-meta.xml
│ │ ├── penpal.js
│ │ └── penpal.resource-meta.xml
│ ├── tabs/
│ │ ├── MA_SetupAuthWizardPageTab.tab-meta.xml
│ │ ├── Mass_Action_Configuration__c.tab-meta.xml
│ │ └── Mass_Action_Log__c.tab-meta.xml
│ ├── testSuites/
│ │ └── Mass_Action_Scheduler_Test_Suite.testSuite-meta.xml
│ └── triggers/
│ ├── MA_BatchApexStatusEventTrigger.trigger
│ ├── MA_BatchApexStatusEventTrigger.trigger-meta.xml
│ ├── MA_JobChangeEventTrigger.trigger
│ ├── MA_JobChangeEventTrigger.trigger-meta.xml
│ ├── MA_MassActionConfigTrigger.trigger
│ ├── MA_MassActionConfigTrigger.trigger-meta.xml
│ ├── MA_MassActionLogTrigger.trigger
│ └── MA_MassActionLogTrigger.trigger-meta.xml
├── licenses/
│ ├── appiphony.txt
│ ├── jquery.txt
│ ├── jsforce.txt
│ ├── penpal.txt
│ ├── sfdc-lax.txt
│ └── soql-parser.txt
├── manifest/
│ └── package.xml
├── scripts/
│ ├── create-scratch-org.sh
│ └── recreate-manifest.sh
└── sfdx-project.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .forceignore
================================================
# List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status
# More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm
#
package.xml
# LWC configuration files
**/jsconfig.json
**/.eslintrc.json
# LWC Jest
**/__tests__/**
# Profiles
**/*.profile
# Demo metadata not core to the project but may be deployed
# in an org as I test the application in a browser.
**DEMO_**
================================================
FILE: .github/CODEOWNERS
================================================
#
# Code Owners File Format
# https://help.github.com/articles/about-code-owners/
#
# A CODEOWNERS file uses a pattern that follows the same rules used in gitignore files.
# The pattern is followed by one or more GitHub usernames or team names using the
# standard @username or @org/team-name format. You can also refer to a user by an
# email address that has been added to their GitHub account, for example user@example.com.
#
* @douglascayers
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: https://www.paypal.me/douglascayers/ # Replace with a single custom sponsorship URL
================================================
FILE: .github/ISSUE_TEMPLATE/BUG_REPORT.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: "discussion \U0001F4AC"
assignees: douglascayers
---
## Summary
## Version of Mass Action Scheduler
## Steps To Reproduce
1. List steps that cause the issue
2. Screen shots are also helpful
## Expected Behavior
## Actual Result
## Source Type
- [ ] Reports
- [ ] List Views
- [ ] SOQL Queries
## Action Type
- [ ] Process Builder & Flows
- [ ] Workflow Rules
- [ ] Email Alerts
- [ ] Quick Actions
- [ ] Apex
## Does your configuration use Named Credentials?
================================================
FILE: .github/ISSUE_TEMPLATE/DOCUMENTATION_REQUEST.md
================================================
---
name: Documentation request
about: Suggest a change to documentation
title: ''
labels: documentation 📓
assignees: ''
---
## Is your documentation request related to an existing wiki page? If yes, which pages?
## Describe the documentation change you're offering or requesting.
================================================
FILE: .github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement ✨
assignees: ''
---
## Is your feature request related to a problem? Please describe.
## Describe the solution you'd like.
## Describe alternatives you've considered.
## Additional context.
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
### What does this pull request do?
### What issues does this pull request fix or reference?
================================================
FILE: .github/workflows/main.yml
================================================
name: CI
on:
# Trigger the workflow on push or pull request,
# but only for the master branch.
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Create Auth URL to DevHub from secrets
run: echo ${{ secrets.SFDX_DEVHUB_URL }} > ./SFDX_DEVHUB_URL.txt
- name: Restore npm Cache
id: cache
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm
- name: Install Salesforce CLI
if: steps.cache.outputs.cache-hit != 'true'
run: npm install sfdx-cli npx
- name: Update Dependencies
run: |
npm update sfdx-cli npx
npx sfdx-cli --version
npx sfdx-cli plugins --core
- name: Authorize DevHub
run: npx sfdx-cli force:auth:sfdxurl:store --sfdxurlfile ./SFDX_DEVHUB_URL.txt --setalias devhub --setdefaultdevhubusername
- name: Create Scratch Org
run: npx sfdx-cli force:org:create --targetdevhubusername devhub --setalias ciorg --setdefaultusername --definitionfile config/project-scratch-def.json --durationdays 1
- name: Push Source
run: npx sfdx-cli force:source:push --targetusername ciorg
- name: Assign Permission Set
run: npx sfdx-cli force:user:permset:assign --targetusername ciorg --permsetname Mass_Action_Admin
- name: Load Test Data
# The test account name, "dca_mass_action: MA Test Account", includes a colon ":", which breaks the yaml parser.
# As workaround, I put the test name as a secret and bind that to the query instead.
run: npx sfdx-cli force:data:record:create --targetusername ciorg --sobjecttype Account --values "Name='${{ secrets.TEST_ACCOUNT_NAME_FOR_APEX_TESTS }}'"
- name: Run Apex Tests
run: npx sfdx-cli force:apex:test:run --targetusername ciorg --codecoverage --resultformat human --suitenames Mass_Action_Scheduler_Test_Suite --outputdir ./test-results/apex --wait 20
- name: Collect Flow Test Coverage
run: |
npx sfdx-cli force:data:soql:query --targetusername ciorg --usetoolingapi --query "SELECT FlowVersionId, FlowVersion.Definition.DeveloperName, MAX(NumElementsCovered) ItemsCovered, MIN(NumElementsNotCovered) ItemsNotCovered FROM FlowTestCoverage WHERE FlowVersionId IN (SELECT ActiveVersionId FROM FlowDefinition) GROUP BY FlowVersionId, FlowVersion.Definition.DeveloperName" --json | jq -r '[ .result.records[] | ( .ItemsCovered + .ItemsNotCovered ) as $totalLines | ( ( .ItemsCovered / $totalLines * 100 ) | floor ) as $coveredPercent | { id: .FlowVersionId, name: ( .DeveloperName + ".flow-meta.xml" ), totalLines: $totalLines, totalCovered: .ItemsCovered, coveredPercent: $coveredPercent } ]' > ./test-results/apex/test-result-flowcoverage.json
npx sfdx-cli force:data:soql:query --targetusername ciorg --usetoolingapi --query "SELECT ActiveVersionId, DeveloperName FROM FlowDefinition WHERE ActiveVersionId NOT IN ( SELECT FlowVersionId FROM FlowTestCoverage ) AND ActiveVersion.ProcessType IN ( 'AutoLaunchedFlow', 'CustomEvent', 'InvocableProcess', 'Workflow' )" --json | jq -r '[ .result.records[] | { id: .ActiveVersionId, name: ( .DeveloperName + ".flow-meta.xml" ), totalLines: 0, totalCovered: 0, coveredPercent: 0 } ]' > ./test-results/apex/test-result-noflowcoverage.json
- name: Upload Code Coverage
# Uploads code coverage results to Codecov.io.
# https://codecov.io/gh/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler
uses: codecov/codecov-action@v2
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Delete Scratch Org
run: npx sfdx-cli force:org:delete --targetdevhubusername devhub --targetusername ciorg --noprompt
================================================
FILE: .gitignore
================================================
# Salesforce DX configuration files.
.sfdx/
.sf/
# IlluminatedCloud configuration files.
.idea/
IlluminatedCloud/
*.iml
# Visual Studio Code configuration files.
.vscode/
# A temp directory where build scripts convert
# source files to metadata format.
mdapi/
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Salesforce Open Source Community Code of Conduct
## About the Code of Conduct
Equality is a core value at Salesforce. We believe a diverse and inclusive
community fosters innovation and creativity, and are committed to building a
culture where everyone feels included.
Salesforce open-source projects are committed to providing a friendly, safe, and
welcoming environment for all, regardless of gender identity and expression,
sexual orientation, disability, physical appearance, body size, ethnicity, nationality,
race, age, religion, level of experience, education, socioeconomic status, or
other similar personal characteristics.
The goal of this code of conduct is to specify a baseline standard of behavior so
that people with different social values and communication styles can work
together effectively, productively, and respectfully in our open source community.
It also establishes a mechanism for reporting issues and resolving conflicts.
All questions and reports of abusive, harassing, or otherwise unacceptable behavior
in a Salesforce open-source project may be reported by contacting the Salesforce
Open Source Conduct Committee at ossconduct@salesforce.com.
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of gender
identity and expression, sexual orientation, disability, physical appearance,
body size, ethnicity, nationality, race, age, religion, level of experience, education,
socioeconomic status, or other similar personal characteristics.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy toward other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Personal attacks, insulting/derogatory comments, or trolling
* Public or private harassment
* Publishing, or threatening to publish, others' private information—such as
a physical or electronic address—without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
* Advocating for or encouraging any of the above behaviors
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned with this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project email
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the Salesforce Open Source Conduct Committee
at ossconduct@salesforce.com. All complaints will be reviewed and investigated
and will result in a response that is deemed necessary and appropriate to the
circumstances. The committee is obligated to maintain confidentiality with
regard to the reporter of an incident. Further details of specific enforcement
policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership and the Salesforce Open Source Conduct
Committee.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home],
version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html.
It includes adaptions and additions from [Go Community Code of Conduct][golang-coc],
[CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc].
This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us].
[contributor-covenant-home]: https://www.contributor-covenant.org (https://www.contributor-covenant.org/)
[golang-coc]: https://golang.org/conduct
[cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md
[microsoft-coc]: https://opensource.microsoft.com/codeofconduct/
[cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Mass Action Scheduler
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
The following is a set of guidelines for contributing to Mass Action Scheduler and its packages,
which are hosted in the [Salesforce Mass Action Scheduler](https://github.com/sfdx-mass-action-scheduler) organization on GitHub.
## Table of Contents
[Code of Conduct](#code-of-conduct)
[How Can I Contribute?](#how-can-i-contribute)
* [Reporting Bugs](#reporting-bugs)
* [Suggesting Enhancements](#suggesting-enhancements)
* [Pull Requests](#pull-requests)
[Styleguides](#styleguides)
[Additional Notes](#additional-notes)
## Code of Conduct
This project and everyone participating in it is governed by the [Code of Conduct](CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code.
## How Can I Contribute?
### Reporting Bugs
This section guides you through submitting a bug report for Mass Action Scheduler.
Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:.
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one.
When you are creating a bug report, please include as many details as possible.
Fill out [the template](.github/ISSUE_TEMPLATE/BUG_REPORT.md), the information it asks for helps us resolve issues faster.
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
#### Before Submitting A Bug Report
* Read the [wiki](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki) for documentation and usage.
* Check the [FAQ](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Frequently-Asked-Questions). Your question might already be answered.
* Review conversations in the [community group](https://success.salesforce.com/_ui/core/chatter/groups/GroupProfilePage?g=0F93A000000LhvN). Your issue might already have been discussed.
* Perform a [cursory search](https://github.com/search?utf8=%E2%9C%93&q=repo%3Asfdx-mass-action-scheduler%2Fsfdx-mass-action-scheduler&type=issues) to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one.
#### How Do I Submit A (Good) Bug Report?
Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/).
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible. For example, when listing steps, **don't just say what you did, but explain how you did it**.
* **Provide specific examples to demonstrate the steps**.
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem.
* **Include any error messages and information from Mass Action Logs.**
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for Mass Action Scheduler,
including completely new features and minor improvements to existing functionality.
Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:.
Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one.
When you are creating an enhancement suggestion, please include as many details as possible.
Fill out [the template](.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md), including how you imagine you would use the requested feature.
#### Before Submitting An Enhancement Suggestion
* Read the [wiki](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki) for documentation and usage. The feature might already exist.
* Perform a [cursory search](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/issues?q=is%3Aopen+is%3Aissue+label%3A%22enhancement+%E2%9C%A8%22) to see if the enhancement has already been requested. If it has **and the request is still open**, add a comment to the existing request instead of opening a new one.
#### How Do I Submit A (Good) Enhancement Suggestion?
Enhancements are tracked as [GitHub issues](https://guides.github.com/features/issues/).
* **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description** of the suggested enhancement in as many details as possible.
* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
* **Explain why this enhancement would be useful.**
* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Mass Action Scheduler which the suggestion is related to.
* **Specify which version of Mass Action Scheduler you're using.** Determine the version number by going to Setup, search for `Installed` in the Quick Find box, then click **Installed Packages**.
### Pull Requests
Please follow these steps to have your contribution considered by the maintainers. We follow the [GitHub Flow](https://guides.github.com/introduction/flow/) to submit pull requests from feature branches from forks of this project.
1. Fork the repo
* https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler
2. Check out a new branch and name it to what you intend to do
* Use one branch per fix / feature
3. Commit your changes
* Follow the [styleguides](#styleguides)
* Provide a git message that explains what you've done
* Commit to your forked repository
4. Push to the branch of your forked repository
5. Make a pull request to the main repository
* Follow all instructions in the [template](.github/PULL_REQUEST_TEMPLATE.md)
While the prerequisites above must be satisfied prior to having your pull request reviewed,
the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted.
## Styleguides
Follow the coding convention and standards of the code in the project.
## Additional Notes
### Issue and Pull Request Labels
Labels help us track and manage issues and pull requests.
Refer to the full list of labels and their descriptions [here](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/labels).
[GitHub search](https://help.github.com/articles/searching-issues/) makes it easy to use labels for finding groups of issues or pull requests you're interested in.
We encourage you to read about [other search filters](https://help.github.com/articles/searching-issues/) which will help you write more focused queries.
================================================
FILE: LICENSE
================================================
BSD 3-Clause License
Copyright (c) 2017-2023, Doug Ayers, douglascayers.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
[![Latest Version][version-shield]][version-url]
[![Salesforce Community Support][community-shield]][community-url]
[![Sponsor][sponsor-shield]][sponsor-url]
[![License][license-shield]][license-url]
[![Build Status][cicd-shield]][cicd-url]
[![Code Coverage][codecov-shield]][codecov-url]
Declaratively schedule process automation from reports and list views!
Explore the docs »
## 📝 Table of Contents
* [About the Project](#-about-the-project)
* [Features](#-features)
* [Roadmap](#-roadmap)
* [Documentation and Discussion](#-documentation-and--discussion)
* [Getting Started](#-getting-started)
- [Prerequisites](#-prerequisites)
- [Install Package](#-install-package)
* [Sponsoring](#-sponsoring)
* [Contributing](#-contributing)
* [Authors](#️-authors)
* [Acknowledgements](#-acknowledgements)
* [License](#-license)
## 🧐 About the Project
#### 🗣 Ethos
Mass Action Scheduler is a [free-as-in-speech](https://www.howtogeek.com/howto/31717/what-do-the-phrases-free-speech-vs.-free-beer-really-mean/) and [open source](https://opensource.com/resources/what-open-source) developed passion project of [Doug Ayers](https://douglascayers.com).
#### 💪 Mission
Put the power of [Batch Apex](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_batch.htm) within reach of [declarative configuration](https://help.salesforce.com/articleView?id=extend_click_intro.htm&type=5).
#### 🚀 Value Proposition
Declaratively schedule Process Builder, Flows, Quick Actions, Email Alerts, Workflow Rules, or Apex to process records from Reports, List Views, SOQL, or Apex.
No more waiting for records to be updated or creating clever workarounds to cause records to be updated to cause these actions to fire.
##### Choose source data to process

##### Choose an action to automate

##### Map fields as inputs to selected action

##### Choose how often to automate action

##### Receive near real-time updates on batch job successes and failures

#### 💡 Inspiration
This project is inspired by the following IdeaExchange ideas. Please vote them up to increase chances this functionality becomes standard on the Salesforce platform. Thank you!
Inspired by Marie Chandra's idea [Ability to Schedule when Process Builder Triggers](https://success.salesforce.com/ideaView?id=08730000000DjEmAAK).
Inspired by Narender Singh's idea [Ability to schedule flows, workflows and processes in process builder](https://success.salesforce.com/ideaView?id=0873A000000EA71QAG).
## 🎈 Features
**Declarative** - no code necessary, never write Batch Apex again for queries that can be expressed in a report or list view and actions that can be expressed with a declarative alternative.
**On Platform** - everything happens in Salesforce so no exporting or uploading data necessary.
**Timely** - run actions [manually](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/On-Demand-Scheduling) or [schedule](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Scheduling) hourly, daily, weekly, or any time in between.
**Versatile** - explore the many [data sources](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Sources) and [actions](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Actions) that can be [scheduled](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Scheduling) with Mass Action Scheduler.
## 🗺 Roadmap
See the [open issues](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/issues) for a list of proposed features (and known issues).
See the [open milestones](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/milestones?state=open) for a list of upcoming planned releases.
## 📘 Documentation and 💬 Discussion
Read the [wiki](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki) for documentation on Mass Action Scheduler.
Read the [FAQ](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Frequently-Asked-Questions) to troubleshoot common technical issues.
Join our [community group](https://success.salesforce.com/_ui/core/chatter/groups/GroupProfilePage?g=0F93A000000LhvN) to discuss and solution with other Mass Action Scheduler users.
Raise well defined issues and ideas via the [issue tracker](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/issues).
## 👋 Getting Started
### 🚨 Prerequisites
There are a few items you need to setup before installing and using this app.
1. You will need to [Enable Lightning Experience](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Prerequisites#1-enable-lightning-experience) because we are using Lightning Components.
2. You will need to [Enable My Domain](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Prerequisites#2-enable-my-domain) because we are using Lightning Components.
3. You will need to [Allow IFraming of Visualforce Pages with Clickjack Protection](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Prerequisites#3-allow-iframing-of-visualforce-pages-with-clickjack-protection) because we iframe pages in Lightning Components.
Please see the [instructions in the wiki](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Prerequisites) for screen shots and step-by-steps.
### 📦 Install Package
1. See the [Release Notes](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Release-Notes) for install links to each package version.
2. Assign the **Mass Action Admin** permission set to users who will configure mass actions.
3. Finish reviewing the [Getting Started](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Getting-Started) instructions.
## 💎 Sponsoring
Mass Action Scheduler is a [free-as-in-speech](https://www.howtogeek.com/howto/31717/what-do-the-phrases-free-speech-vs.-free-beer-really-mean/) and [open source](https://opensource.com/resources/what-open-source) developed passion project of [Doug Ayers](https://douglascayers.com).
If you've found value in my open source projects, please consider showing your support:
* ⭐️ [Star](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler) this project on GitHub
* 📣 [Tweet](https://twitter.com/intent/tweet/?url=https%3A%2F%2Fgithub.com%2Fsfdx-mass-action-scheduler%2Fsfdx-mass-action-scheduler&text=%F0%9F%9A%80%20Declaratively%20schedule%20Process%20Builder%2C%20Flows%2C%20Quick%20Actions%2C%20Email%20Alerts%2C%20Workflow%20Rules%2C%20or%20Apex%20to%20process%20records%20from%20Reports%2C%20List%20Views%2C%20SOQL%2C%20or%20Apex%20with%20%23MassActionScheduler%20by%20%40DouglasCAyers&related=douglascayers%2Csalesforcedevs&hashtags=salesforce) this project to your followers
* Contribute a ☕️ or 🌮 via my [virtual tip jar on PayPal](https://www.paypal.me/douglascayers/)
Thank you! ❤️
https://douglascayers.com/thanks-for-your-support/
## 🙏 Contributing
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
Please see the [guidelines for contributing](CONTRIBUTING.md) for more details.
For documentation contributions (the [wiki](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki)), please [open an issue](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/issues) with your suggested changes.
For code contributions, please follow the [GitHub flow](https://help.github.com/en/articles/github-flow):
1. Fork this project and [install the source code](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Install-Source-Code).
2. Create your feature branch (`git checkout -b feature/AmazingFeature`).
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
4. Push to your feature branch (`git push origin feature/AmazingFeature`).
5. Open a pull request to the `develop` branch.
## ✍️ Authors
[Doug Ayers](https://douglascayers.com) develops and maintains the project.
See also the list of [contributors](https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/contributors) who participated in this project.
## 🎉 Acknowledgements
[Appiphony](http://www.lightningstrike.io) for developing the Strike Wizard component based on Lightning Design System [Path blueprint](https://www.lightningdesignsystem.com/components/path/).
[Salesforce Foundation](https://github.com/SalesforceFoundation/CampaignTools) for developing tools for querying Salesforce Reports API in Apex.
[Shinichi Tomita](https://twitter.com/stomita) for developing [jsforce](https://jsforce.github.io/) and [soql-parse](https://github.com/stomita/soql-parse) libraries for easy use of Salesforce REST APIs in JavaScript.
[jQuery](https://jquery.com/) for developing jQuery library.
[Aaron Hardy](https://twitter.com/aaronius) for developing [Penpal](https://github.com/Aaronius/penpal), a promise-based library for securely communicating with iframes via postMessage.
## 👀 License
The source code is licensed under the [BSD 3-Clause License](LICENSE).
[version-shield]: https://img.shields.io/github/tag/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler.svg?label=release&color=green
[version-url]: https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/wiki/Release-Notes
[license-shield]: https://img.shields.io/github/license/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler.svg?label=license&color=green
[license-url]: LICENSE
[community-shield]: https://img.shields.io/badge/-Join_our_Community-blue.svg?logo=salesforce&logoColor=white
[community-url]: https://success.salesforce.com/_ui/core/chatter/groups/GroupProfilePage?g=0F93A000000LhvN
[cicd-shield]: https://img.shields.io/github/workflow/status/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/CI?logo=github
[cicd-url]: https://circleci.com/gh/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler
[codecov-shield]: https://img.shields.io/codecov/c/github/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler.svg?logo=codecov
[codecov-url]: https://codecov.io/gh/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler
[sponsor-shield]: https://img.shields.io/badge/-💜_Sponsor_this_project-ff69b4.svg
[sponsor-url]: https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/blob/master/README.md#-sponsoring
================================================
FILE: _config.yml
================================================
theme: jekyll-theme-cayman
title: Mass Action Scheduler
================================================
FILE: _layouts/default.html
================================================
{% seo %}
{% if site.github.is_project_page %}
View on GitHubInstall PackageDocumentation❤️ Sponsor
{% endif %}
{% if site.show_downloads %}
Download .zipDownload .tar.gz
{% endif %}
{{ content }}
================================================
FILE: config/project-scratch-def.json
================================================
{
"orgName": "Mass Action Scheduler",
"edition": "Enterprise",
"hasSampleData": false,
"features": [],
"settings": {
"mobileSettings": {
"enableS1EncryptedStoragePref2": false
},
"securitySettings": {
"sessionSettings": {
"enableClickjackNonsetupUser": false,
"enableClickjackNonsetupUserHeaderless": false
}
}
}
}
================================================
FILE: force-app/main/default/applications/Mass_Action_Scheduler.app-meta.xml
================================================
Mass_Action_Configuration__cDeclaratively schedule records from Reports or List Views to be processed by Workflow Rules, Process Builder, Flows, and Apex. https://douglascayers.comfalsefalseMass_Action_Configuration__cMA_SetupAuthWizardPageTabMass_Action_Log__cstandard-report
================================================
FILE: force-app/main/default/applications/Mass_Action_Scheduler_Lightning.app-meta.xml
================================================
ViewAction override created by Lightning App Builder during activation.Mass_Action_Configuration_Record_Page_One_ColumnLargefalseFlexipageMass_Action_Configuration__cViewAction override created by Lightning App Builder during activation.Mass_Action_Log_Record_PageLargefalseFlexipageMass_Action_Log__c#2C94BAmaslogominimal1trueDeclaratively schedule process automation from reports and list viewsLargefalsefalseStandardallMass_Action_Configuration__cMA_SetupAuthWizardPageTabMass_Action_Log__cstandard-reportLightning
================================================
FILE: force-app/main/default/aura/LC_API/LC_API.cmp
================================================
{!v.body}
================================================
FILE: force-app/main/default/aura/LC_API/LC_API.cmp-meta.xml
================================================
54.0LC_API
================================================
FILE: force-app/main/default/aura/LC_API/LC_APIController.js
================================================
/*
Author: Doug Ayers
Website: https://douglascayers.com
GitHub: https://github.com/douglascayers/sfdx-lightning-api-component
License: BSD 3-Clause License
*/
({
/**
* Called once during component initialization phase.
*/
onInit: function( component, event, helper ) {
helper._penpal = {};
helper.makeApexRequest( component, 'c.getVisualforceDomainURL' ).then( $A.getCallback( function( vfDomainURL ) {
component.set( 'v.iframeSrc', `${vfDomainURL}/apex/LC_APIPage` );
})).catch( $A.getCallback( function( err ) {
console.error( 'LC_API: Error determining visualforce domain', err );
}));
},
/**
* Called once after ltng:require has loaded scripts.
*/
onScriptsLoaded: function( component, event, helper ) {
},
/**
* Called each time the component renders itself.
*/
onRender: function( component, event, helper ) {
const isPenpalFrameCreated = component.get( 'v.penpalFrameCreated' );
// For Penpal to operate correctly, you must ensure that `connectToChild`
// is called before the iframe has called `connectToParent`.
// Since the iframe source is calculated asynchronously,
// we listen to the component's render events and each time
// check if the iframe source is ready, and if so, then we initialize
// penpal to connect this component to the iframe.
// Since we only want to do this once, we also set the initialized flag.
if ( !isPenpalFrameCreated ) {
const container = component.find( 'penpalFrameContainer' );
const iframeSrc = component.get( 'v.iframeSrc' );
// Ensure the container element has rendered otherwise we can't
// append child elements to it. And wait for the iframe source to
// be available otherwise no reason to create the iframe element.
if ( !$A.util.isEmpty( container ) && !$A.util.isEmpty( iframeSrc ) ) {
$A.createComponent(
"aura:html",
{
"aura:id": "penpalFrame",
"tag": "iframe",
"HTMLAttributes": {
"src": iframeSrc
}
},
function( iframeCmp, status, errorMessage ) {
// This callback happened asynchronously, so make one
// more check on whether the penpal frame has been initialized or not
// in the off chance a separate render cycle got here before this one.
const isPenpalFrameCreated = component.get( 'v.penpalFrameCreated' );
if ( isPenpalFrameCreated ) {
console.log( 'LC_API: iframe is already initialized' );
} else if ( status === 'SUCCESS' ) {
// At this point, the iframe component has been constructed
// but not yet been rendered, so we don't have access to the
// HTML iframe element yet. We need to wait for another render cycle,
// that is, we need to wait for the render() method to be called again
// after we append the new iframe component to the body of its container.
// Once we're able to find the 'penpalFrame' on the page then
// we can proceed with the rest of the penpal initialization.
component.set( 'v.penpalFrameCreated', true );
container.set( 'v.body', [ iframeCmp ] );
console.info( 'LC_API: iframe initialized' );
} else if ( status === 'INCOMPLETE' ) {
console.warn( 'LC_API: No response from server or client is offline' );
} else if ( status === 'ERROR' ) {
console.error( 'LC_API: Error creating iframe: ' + errorMessage );
}
}
);
} // else, iframe source is empty, keep waiting
} else {
const isPenpalFrameConnected = component.get( 'v.penpalFrameConnected' );
const iframeCmp = component.find( 'penpalFrame' );
if ( !$A.util.isEmpty( iframeCmp ) && !isPenpalFrameConnected ) {
const connection = Penpal.connectToChild({
// The iframe to which a connection should be made
iframe: iframeCmp.getElement()
});
helper._penpal.connection = connection;
connection.promise.then( $A.getCallback( function( child ) {
// Cache a reference to the child so that we can
// use it in the restRequest/fetchRequest methods,
// as well as be able to destroy it when this component unrenders.
helper._penpal.child = child;
console.info( 'LC_API: connected to iframe ' + iframeCmp.getGlobalId() );
component.set( 'v.penpalFrameConnected', true );
})).catch( $A.getCallback( function( err ) {
console.error( 'LC_API: Error establishing connection to iframe ' + iframeCmp.getGlobalId(), err );
component.set( 'v.penpalFrameConnected', false );
}));
}
}
},
onRestRequest: function( component, event, helper ) {
const params = event.getParam( 'arguments' );
return helper.handleRestRequest( component, params.request );
},
onFetchRequest: function( component, event, helper ) {
const params = event.getParam( 'arguments' );
return helper.handleFetchRequest( component, params.request );
}
})
/*
BSD 3-Clause License
Copyright (c) 2017-2023, Doug Ayers, douglascayers.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
================================================
FILE: force-app/main/default/aura/LC_API/LC_APIHelper.js
================================================
/*
Author: Doug Ayers
Website: https://douglascayers.com
GitHub: https://github.com/douglascayers/sfdx-lightning-api-component
License: BSD 3-Clause License
*/
({
handleRestRequest: function( component, request ) {
const helper = this;
const defaultRequest = {
'method' : 'get'
};
const defaultHeaders = {
'Content-Type': 'application/json'
};
request = Object.assign( {}, defaultRequest, request );
request.headers = Object.assign( {}, defaultHeaders, request.headers );
return helper.getPenpalChild().then( $A.getCallback( function( child ) {
return helper.makePenpalRequest( 'rest', child, request );
}));
},
handleFetchRequest: function( component, request ) {
const helper = this;
return helper.getPenpalChild().then( $A.getCallback( function( child ) {
return helper.makePenpalRequest( 'fetch', child, request );
}));
},
// ------------------------------------------------------------
/**
* For internal use.
* Returns a promise waiting for the parent-child penpal handshake to complete
* then resolves with reference to the penpal child for making requests.
*/
getPenpalChild: function() {
const helper = this;
return new Promise( $A.getCallback( function( resolve, reject ) {
let child = helper._penpal.child;
if ( child ) {
resolve( child );
} else {
// all time values in milliseconds
const timeout = 10000; // ten seconds
const pollFrequency = 500; // half a second
const startTime = new Date().getTime();
const endTime = startTime + timeout;
const timerId = setInterval( $A.getCallback( function() {
child = helper._penpal.child;
if ( child ) {
// parent-child penpal handshake now complete
clearInterval( timerId );
resolve( child );
} else {
// check if we have exceeded our timeout
const currentTime = new Date().getTime();
if ( currentTime > endTime ) {
clearInterval( timerId );
reject( 'LC_API: Timeout trying to establish connection to iframe' );
}
// else, keep polling
}
}), pollFrequency );
}
}));
},
/**
* For internal use.
* Returns a promise waiting for the parent-child penpal request to complete
* then resolves with response from the child iframe.
*/
makePenpalRequest: function( requestType, child, request ) {
let p;
if ( requestType === 'rest' ) {
p = child.restRequest( request );
} else if ( requestType === 'fetch' ) {
p = child.fetchRequest( request );
} else {
p = Promise.resolve({
success: false,
data: 'LC_API: Invalid request type: ' + requestType
});
}
return p.then( $A.getCallback( function( response ) {
if ( response.success ) {
return response.data;
} else {
return Promise.reject( response.data );
}
}));
},
/**
* For internal use.
* Returns a promise waiting for the Apex request to complete
* then resolves with the JSON response, or rejects if any error.
*
* @param component
* (required) Reference to the component who has access to the Aura Enabled method specified by `actionName`.
* @param actionName
* (required) Name of the Aura Enabled Apex method in form `c.methodName`.
* @param params
* (optional) JSON map of request parameters to pass to the Apex action.
* @param options
* (optional) JSON map of options to customize the request.
* `background` set to true will execute request in background thread.
* `storable` set to true will cache the response.
*/
makeApexRequest: function( component, actionName, params, options ) {
const helper = this;
return new Promise( $A.getCallback( function( resolve, reject ) {
const action = component.get( actionName );
if ( params ) {
action.setParams( params );
}
if ( options ) {
if ( options.background ) { action.setBackground(); }
if ( options.storable ) { action.setStorable(); }
}
action.setCallback( helper, function( response ) {
if ( component.isValid() && response.getState() === 'SUCCESS' ) {
resolve( response.getReturnValue() );
} else {
console.error( 'Error calling action "' + actionName + '" with state: ' + response.getState() );
helper.logActionErrors( response.getError() );
reject( response.getError() );
}
});
$A.enqueueAction( action );
}));
},
/**
* For internal use.
* Logs to console errors object.
* Errors may be a String or Array.
*/
logActionErrors: function( errors ) {
if ( errors ) {
if ( errors.length > 0 ) {
for ( var i = 0; i < errors.length; i++ ) {
console.error( 'Error: ' + errors[i].message );
}
} else {
console.error( 'Error: ' + errors );
}
} else {
console.error( 'Unknown error' );
}
}
})
/*
BSD 3-Clause License
Copyright (c) 2017-2023, Doug Ayers, douglascayers.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
================================================
FILE: force-app/main/default/aura/LC_API/LC_APIRenderer.js
================================================
/*
Author: Doug Ayers
Website: https://douglascayers.com
GitHub: https://github.com/douglascayers/sfdx-lightning-api-component
License: BSD 3-Clause License
*/
({
unrender: function( component, helper ) {
this.superUnrender();
// When component unrenders then cleanup penpal
// resources by destroying the connection and nulling out
// the helper's cached reference to the connection and child.
// This ensures that the helper.handleXyzRequest(..) methods
// wait appropriately for the new parent-child handshake to complete
// when this component is re-initialized and scripts are loaded.
if ( helper._penpal && helper._penpal.connection ) {
helper._penpal.connection.destroy();
helper._penpal = {};
}
}
})
/*
BSD 3-Clause License
Copyright (c) 2017-2023, Doug Ayers, douglascayers.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
================================================
FILE: force-app/main/default/aura/LC_URL/LC_URL.cmp
================================================
================================================
FILE: force-app/main/default/aura/LC_URL/LC_URL.cmp-meta.xml
================================================
54.0LC_URL
================================================
FILE: force-app/main/default/aura/LC_URL/LC_URLController.js
================================================
/*
Author: Doug Ayers
Website: https://douglascayers.com
GitHub: https://github.com/douglascayers/sfdx-lightning-api-component
License: BSD 3-Clause License
*/
({
onGetUrlInfo: function( component, event, helper ) {
return component.lax.enqueue( 'c.getUrlInfo' );
}
})
/*
BSD 3-Clause License
Copyright (c) 2017-2023, Doug Ayers, douglascayers.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
================================================
FILE: force-app/main/default/aura/MA_CheckForPackageUpdatesCmp/MA_CheckForPackageUpdatesCmp.cmp
================================================
Read the wiki page for further documentation on Mass Action Scheduler.
Read the FAQ page to help troubleshoot technical issues.
Active configurations can be run manually or on a schedule.
Inactive configurations do not run.
If a configuration is scheduled, it's Apex job is unscheduled for future runs until you re-activate it again.
Batch Size controls the number of times the target action from the Choose Action step is invoked per save transaction.
As when using Data Loader, you may need to reduce the batch size to stay within governor limits.
Named Credential is optional and is a way to specify the running user performing the actions.
That is to say the user who has their name stamped as the "Last Modified By" on any updated records
by the target action from the Choose Action step.
If not specified, then the user who last activates the configuration, or who manually runs it, is the running user.
Select the source of the records you want to process.
List Views are easy to create and support simple filter criteria.
Learn how to create list views with the Create and Customize List Views unit on Trailhead.
SOQL queries are an advanced technique to search for specific records. Simple queries can be helpful when you don't want to deal with the overhead of creating a list view or a report.
Learn how to write SOQL queries with the Write SOQL Queries unit on Trailhead.
Apex classes that implement Iterable<Map<String, Object>> are an advanced technique that give you full control over what the source data is.
For example, if you need to make multiple SOQL queries, apply complex data filtering, or retrieve data from an external web service with http callouts.
Learn how to develop custom iterators with the Apex Developer Guide.
Stay within limits. If your source type would return more than its record limit then either modify filters to reduce the row count or use a different source type.
For example, a report that returns 30 thousand records would need to be split into three smaller reports or switch to another source type.
Up to 50 million records.
Class must implement Iterable<Map<String, Object>> and provide a no-argument constructor.
Alias SOQL aggregate functions. Aggregate functions like COUNT, MIN, MAX, AVG, SUM and others
must be aliased to be selectable in the Field Mappings step.
For example, consider the query SELECT COUNT(Id) RecordCount, Type FROM Account GROUP BY Type.
Without the RecordCount alias then only the Type field would be available on the Field Mappings step.
Test your data source before automating.
Confirm fields and filters by first previewing list views and running reports.
Execute SOQL queries in a tool like Developer Console or Data Loader.
Select the target action you want to process the source records.
Note, Process Builders and Flows are grouped together because the REST API does not distinguish between them.
You may like to use a naming convention to help you tell them apart. For example, adding the word "Flow" to the names of your flows (e.g. "My Account Flow").
If you don't see the action you want listed, ensure that it is active and is a supported type.
For anonymous blocks, your script must include the following method definition,
which will be passed the current batch of source records. Each execution of your script occurs in its own transaction.
void execute( List<Map<String, Object>> sourceRecordsBatch ) {
// your logic here
}
Apex classes and anonymous blocks are invoked once per batch of source records, just like the execute method of a Batchable Class or a Bulk Trigger.
However, unlike batchable or trigger code, no state is preserved within the script between executions. Though you may choose to manage and preserve state yourself between executions via DML to records or custom settings.
Map source fields as inputs to the target action.
For example, some action types like Process Builder, Workflow Rules, and Email Alerts require a record ID to be mapped to them from the source data.
Don't see the Source Field you're looking for?
Confirm the field is included in your source type.
If using a Named Credential, ensure that user also has appropriate object and field permissions.
If using SOQL, alias the aggregate functions. Aggregate functions like COUNT, MIN, MAX, AVG, SUM and others
must be aliased to be selectable in the Field Mappings step.
For example, consider the query SELECT COUNT(Id) RecordCount, Type FROM Account GROUP BY Type.
Without the RecordCount alias then only the Type field would be available on the Field Mappings step.
If using SOQL, child relationship queries are not shown in the Field Mappings step.
For example, consider the query SELECT Id, Name, ( SELECT FirstName FROM Contacts ) FROM Account.
The child relationship query SELECT FirstName FROM Contacts is ignored.
Therefore, and for performance, do not use child relationship queries in your SELECT statement.
For Quick Actions, ensure the field is on the action's layout.
For Invocable Apex, ensure the class variable has the @InvocableVariable annotation.
For Anonymous Apex, the field mappings step is not used. Instead, your script handles all field mapping and logic within its execute method.
Choose the schedule frequency when to run this Mass Action Configuration.
On Demand runs only when you click the Run button or if you use Process Builder, Flow, or Apex to
invoke the provided MA_RunConfigInvocable invocable Apex class (labeled "MAS: Run Mass Action").
Scheduled provides simple options to choose the hour, day, month, and year to run the configuration repeatedly.
Custom allows you to specify your own cron expression which allows more advanced options than the provided simple scheduler.
Learn more about how to write cron expressions in the "Using the System.Schedule Method" section of the Apex Scheduler documentation.
Note, only active configurations will run regardless the schedule frequency chosen.
Salesforce has a limit on the number of scheduled Apex jobs at one time. Traditionally this is 100 scheduled jobs.
If you encounter an error trying to save an active configuration with a scheduled or custom frequency,
you may have reached the limit and need to either deactivate other configurations or unschedule other Apex jobs in your org.
Learn more about these limits in the "Apex Scheduler Limits" section of the Apex Scheduler documentation.
Example cron expressions for custom schedule frequency:
For each source record, all active Workflow Rules will run whose Rule Criteria matches the record.
Evaluation Criteria, which normally decides when a rule fires, is ignored because you are explicitly running the rules with Mass Action Scheduler.
Learn more with the Set the Criteria for Your Workflow Rule help article.
The selected action type has no fields to map.
You may proceed to schedule this configuration.
Source Field
Target Field
*
*{!targetFieldMapping.targetField.label}
Select the hours, days of month or weekdays, and months
that you want to schedule the action to run.
================================================
FILE: force-app/main/default/aura/MA_EditConfigCmp/MA_EditConfigCmp.cmp-meta.xml
================================================
54.0MA_EditConfigCmp
================================================
FILE: force-app/main/default/aura/MA_EditConfigCmp/MA_EditConfigCmp.css
================================================
/*
Author: Doug Ayers
Website: https://douglascayers.com
GitHub: https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler
License: BSD 3-Clause License
*/
/**
* Make it more obvious that the user can
* click to select an option from the picklist.
*/
.THIS lightning-combobox * {
cursor: pointer;
}
/**
* W-5212157
* https://salesforce.stackexchange.com/questions/220616/summer-18-lightning-input-variant-label-hidden-hides-the-label-but-occupies-th/221202#221202
*/
.THIS .label-hidden > label,
.THIS .label-hidden legend {
display: none;
}
/**
* The strike_wizard component applies this class
* to the chevrons that are incomplete. To further
* express to users they cannot jump ahead then
* change mouse cursor on hover.
*/
.THIS .slds-is-incomplete * {
cursor: not-allowed;
}
/**
* Styling copied from Trailhead website.
*/
.THIS .keyword {
color: #d14;
background-color: #f7f7f9;
border: t(borderWidthThin) solid #e1e1e8;
border-radius: t(borderRadiusSmall);
padding: t(varSpacingXxxSmall) t(varSpacingXxSmall);
}
/**
* Renders newlines and whitespace indentation
* similar to the
tag but without the
* ridiculous leading whitespace.
*/
.THIS code.block {
color: #d14;
white-space: pre-line;
display: block;
}
/**
* This CSS selector places the tag name after our class name
* because the `class` attribute we put on
* is applied to the outer DOM element and not on the actual
* `textarea` tag within the component. Therefore, our selector
* needs to cascade to the `textarea` elements in the component.
*/
.THIS textarea[name="inputTargetApexScript"],
.THIS textarea[name="inputSourceSoqlQuery"] {
height: 200px;
}
/*
BSD 3-Clause License
Copyright (c) 2017-2023, Doug Ayers, douglascayers.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
================================================
FILE: force-app/main/default/aura/MA_EditConfigCmp/MA_EditConfigCmpController.js
================================================
/*
Author: Doug Ayers
Website: https://douglascayers.com
GitHub: https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler
License: BSD 3-Clause License
*/
({
onInit : function( component, event, helper ) {
var recordId = component.get( 'v.recordId' );
// initialize wizard to first step
var wizard = component.find( 'wizard' );
wizard.moveToStage( 0 );
component.set( 'v.wizardActiveStageIndex', 0 );
Promise.resolve()
.then( $A.getCallback( function() {
let promises = [];
promises.push( helper.getObjectDescribeAsync( component )
.then( $A.getCallback( function( objectDescribe ) {
component.set( 'v.objectDescribe', objectDescribe );
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Getting Object Describe', err, 'error' );
}))
);
promises.push( helper.getRecordAsync( component, recordId )
.then( $A.getCallback( function( record ) {
component.set( 'v.record', record );
component.set( 'v.sourceType', record.sourceType );
component.set( 'v.targetType', record.targetType );
component.set( 'v.targetSobjectType', record.targetSobjectType );
component.set( 'v.targetInvocableAction', record.targetActionName );
if ( record.targetType === 'Apex' ) {
if ( !$A.util.isEmpty( record.targetActionName ) ) {
component.set( 'v.targetApexType', 'Invocable' );
}
else if ( !$A.util.isEmpty( record.targetApexScript ) ) {
component.set( 'v.targetApexType', 'Anonymous' );
}
}
helper.initScheduleOptions( component );
return record;
})).then( $A.getCallback( function( record ) {
if ( !$A.util.isUndefinedOrNull( record.sourceReportID ) ) {
return helper.getReportAsync( component, record.sourceReportID )
.then( $A.getCallback( function( report ) {
if ( !$A.util.isUndefinedOrNull( report ) ) {
component.set( 'v.sourceReport', report );
component.set( 'v.sourceReportId', ( report.Id && report.Id.substring( 0, 15 ) ) );
component.set( 'v.sourceReportFolderId', ( report.OwnerId && report.OwnerId.substring( 0, 15 ) ) );
component.set( 'v.sourceReportColumnName', record.sourceReportColumnName );
}
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Getting Report', err, 'error' );
}));
}
if ( !$A.util.isUndefinedOrNull( record.sourceListViewID ) ) {
return helper.getListViewAsync( component, record.sourceListViewID )
.then( $A.getCallback( function( listView ) {
if ( !$A.util.isUndefinedOrNull( listView ) ) {
component.set( 'v.sourceListView', listView );
component.set( 'v.sourceListViewId', ( listView.Id && listView.Id.substring( 0, 15 ) ) );
component.set( 'v.sourceListViewSobjectType', listView.SobjectType );
}
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Getting List View', err, 'error' );
}));
}
})).then( $A.getCallback( function() {
// avoid race condition where as the page loads,
// several change handlers call controller methods
// and those methods end up reading/writing attribtues
// before the above async operations have completed.
// https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler/issues/94
component.set( 'v.didInitConfig', true );
return Promise.all([
helper.handleSourceTypeChange( component ),
helper.handleTargetTypeChange( component )
]);
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Getting Mass Action Configuration', err, 'error' );
}))
);
promises.push( helper.getNamedCredentialsAsync( component )
.then( $A.getCallback( function( namedCredentials ) {
var emptyOption = {
'label': '--None--',
'value': null
};
component.set( 'v.targetNamedCredentials', [ emptyOption ].concat( namedCredentials ) );
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Getting Named Credentials', err, 'error' );
}))
);
return Promise.all( promises );
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error initializing component', err, 'error' );
}));
},
handleNavigationButtonClick : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
var wizard = component.find( 'wizard' );
var currentStageIndex = wizard.get( 'v.activeChevron' );
var button = event.getSource();
var buttonLabel = button.get( 'v.label' );
if ( buttonLabel == 'Previous' ) {
wizard.moveToStage( currentStageIndex - 1 );
} else if ( buttonLabel == 'Next' ) {
var inputCmps = []; // fields to validate to proceed to next step
if ( currentStageIndex === 0 ) { // Details
inputCmps = [
component.find( 'inputName' ),
component.find( 'inputDeveloperName' ),
component.find( 'inputDescription' ),
component.find( 'inputActive' ),
component.find( 'inputBatchSize' ),
component.find( 'inputNamedCredential' )
];
} else if ( currentStageIndex === 1 ) { // Choose Source
inputCmps = [
component.find( 'inputSourceType' ),
component.find( 'inputSourceReportFolder' ),
component.find( 'inputSourceReport' ),
component.find( 'inputSourceReportColumn' ),
component.find( 'inputSourceListViewSobjectType' ),
component.find( 'inputSourceListView' ),
component.find( 'inputSourceSoqlQuery' ),
component.find( 'inputSourceApexClass' )
];
} else if ( currentStageIndex === 2 ) { // Choose Action
inputCmps = [
component.find( 'inputTargetType' ),
component.find( 'inputTargetSobjectType' ),
component.find( 'inputTargetAction' ),
component.find( 'inputTargetApexType' ),
component.find( 'inputTargetApexScript' )
];
} else if ( currentStageIndex === 3 ) { // Field Mappings
var inputSourceFieldNames = component.find( 'inputMappingSourceFieldName' );
if ( $A.util.isArray( inputSourceFieldNames ) ) {
for ( var i = 0; i < inputSourceFieldNames.length; i++ ) {
inputCmps.push( inputSourceFieldNames[i] );
}
} else {
inputCmps.push( inputSourceFieldNames );
}
}
helper.validateInputsAsync( component, inputCmps )
.then( $A.getCallback( function( validationResult ) {
var isValidToProceed = !validationResult.hasErrors;
if ( isValidToProceed ) {
return Promise.resolve()
.then( $A.getCallback( function() {
// if advancing to field mappings section then
// determine the action inputs and any current mappings
if ( currentStageIndex === 2 ) {
return helper.renderTargetFieldMappingsAsync( component );
}
})).then( $A.getCallback( function() {
wizard.advanceProgress();
}));
} else {
validationResult.components.forEach( function( validationComponentResult ) {
if ( validationComponentResult.hasError ) {
helper.toastMessage( 'Step Incomplete', validationComponentResult.messageWhenInvalid, 'error' );
validationComponentResult.component.reportValidity();
}
});
}
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Advancing to Next Step', err, 'error' );
}));
}
},
handleSaveButtonClick : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
var inputCmps = [
component.find( 'inputScheduleFrequency' ),
component.find( 'inputScheduleHourOfDay' ),
component.find( 'inputScheduleWeekday' ),
component.find( 'inputScheduleDayOfMonth' ),
component.find( 'inputScheduleMonthOfYear' ),
component.find( 'inputScheduleCron' )
];
helper.validateInputsAsync( component, inputCmps )
.then( $A.getCallback( function( validationResult ) {
var isValidToSave = !validationResult.hasErrors;
if ( isValidToSave ) {
return helper.saveRecordAsync( component )
.then( $A.getCallback( function( result ) {
if ( result.success ) {
helper.toastMessage( 'Save Successful', '', 'success' );
// Cause lightning data service to invalidate it's cache.
// I added this after realizing the compact layout was not
// picking up changes to fields by this component.
// I started out firing the force:refreshView event but
// that only worked if the record already existed, if we
// just saved a new record then we needed to still navigate to it.
// And I didn't know how to listen for the refreshView event to complete
// but I did find that I could use a callback in the LDS reloadRecord method.
var lds = component.find( 'lds' );
lds.set( 'v.recordId', result.recordId );
lds.reloadRecord( true, function() {
helper.navigateToRecord( result.recordId );
});
} else {
helper.toastMessage( 'Save Failed', '', 'error' );
}
}));
} else {
validationResult.components.forEach( function( validationComponentResult ) {
if ( validationComponentResult.hasError ) {
helper.toastMessage( 'Step Incomplete', validationComponentResult.messageWhenInvalid, 'error' );
validationComponentResult.component.reportValidity();
}
});
}
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Saving Configuration', err, 'error' );
}));
},
// ----------------------------------------------------------------------------------
handleInputNameFieldBlur : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
var inputCmp = event.getSource();
var inputValue = inputCmp.get( 'v.value' );
// predict the developer name from the name, a familiar feature to admins
if ( !helper.isEmpty( inputValue ) && helper.isEmpty( component.get( 'v.record.developerName' ) ) ) {
component.set( 'v.record.developerName', inputValue.trim().replace( /[ ]+/g, '_' ) );
}
},
handleOnBlurInputSourceSoqlQuery : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
var inputCmp = event.getSource();
var inputValue = inputCmp.get( 'v.value' );
if ( !$A.util.isUndefinedOrNull( inputValue ) ) {
inputCmp.set( 'v.value', inputValue.trim() );
}
},
handleOnBlurInputTargetApexScript : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
var inputCmp = event.getSource();
var inputValue = inputCmp.get( 'v.value' );
if ( !$A.util.isUndefinedOrNull( inputValue ) ) {
inputCmp.set( 'v.value', inputValue.trim() );
}
},
handleInputListBoxChanged : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
var selectedOptions = event.getParam( 'value' );
if ( !helper.isEmpty( selectedOptions ) ) {
selectedOptions.sort();
}
},
// ----------------------------------------------------------------------------------
handleSourceTypeChange : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
helper.handleSourceTypeChange( component );
},
// ----------------------------------------------------------------------------------
handleSourceReportFolderChange : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
helper.handleSourceReportFolderChange( component );
},
handleSourceReportChange : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
helper.handleSourceReportChange( component );
},
// -----------------------------------------------------------------
handleSourceListViewSobjectTypeChange : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
helper.handleSourceListViewSobjectTypeChange( component );
},
handleSourceListViewChange : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
helper.handleSourceListViewChange( component );
},
// ----------------------------------------------------------------------------------
handleTargetTypeChange : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
helper.handleTargetTypeChange( component );
},
handleTargetApexTypeChange : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
helper.handleTargetTypeChange( component );
},
handleTargetSobjectTypeChange : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
helper.renderTargetInvocableActions( component );
},
handleTargetTypeRequiresSobjectChange : function( component, event, helper ) {
if ( component.get( 'v.didInitConfig' ) !== true ) {
return;
}
helper.renderTargetSobjectTypes( component );
}
})
/*
BSD 3-Clause License
Copyright (c) 2017-2023, Doug Ayers, douglascayers.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
================================================
FILE: force-app/main/default/aura/MA_EditConfigCmp/MA_EditConfigCmpHelper.js
================================================
/*
Author: Doug Ayers
Website: https://douglascayers.com
GitHub: https://github.com/sfdx-mass-action-scheduler/sfdx-mass-action-scheduler
License: BSD 3-Clause License
*/
({
initScheduleOptions : function( component ) {
var helper = this;
var scheduleOptionsHourOfDay = [];
var scheduleOptionsDayOfMonth = [];
var scheduleOptionsMonthOfYear = [];
var scheduleOptionsDayOfWeek = [];
for ( let i = 0; i < 24; i++ ) {
scheduleOptionsHourOfDay.push({
'label' : ( i == 0 ? '12:00 AM' : i == 12 ? '12:00 PM' : ( i < 12 ? i + ':00 AM' : ( i - 12 ) + ':00 PM' ) ).padStart( 8, '0' ),
'value' : i.toString().padStart( 2, '0' ) + '.' + i.toString()
});
}
for ( let i = 1; i <= 31; i++ ) {
scheduleOptionsDayOfMonth.push({
'label' : i.toString(),
'value' : i.toString().padStart( 2, '0' ) + '.' + i.toString()
});
}
var monthValues = [ 'JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC' ];
var localeMonthNames = $A.get( '$Locale.nameOfMonths' );
for ( let i = 0; i < localeMonthNames.length; i++ ) {
if ( !helper.isEmpty( localeMonthNames[i].fullName ) ) {
scheduleOptionsMonthOfYear.push({
'label' : localeMonthNames[i].fullName.toUpperCase(), // display in user's locale
'value' : i.toString().padStart( 2, '0' ) + '.' + monthValues[i] // but capture in english for cron expr.
}); // left pad with number for easy sorting
}
}
var weekdayValues = [ 'SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT' ];
var localeWeekdayNames = $A.get( '$Locale.nameOfWeekdays' );
for ( let i = 0; i < localeWeekdayNames.length; i++ ) {
if ( !helper.isEmpty( localeWeekdayNames[i].fullName ) ) {
scheduleOptionsDayOfWeek.push({
'label' : localeWeekdayNames[i].fullName.toUpperCase(), // display in user's locale
'value' : i.toString().padStart( 2, '0' ) + '.' + weekdayValues[i] // but capture in english for cron expr.
}); // left pad with number for easy sorting
}
}
component.set( 'v.scheduleOptionsHourOfDay', scheduleOptionsHourOfDay );
component.set( 'v.scheduleOptionsDayOfMonth', scheduleOptionsDayOfMonth );
component.set( 'v.scheduleOptionsMonthOfYear', scheduleOptionsMonthOfYear );
component.set( 'v.scheduleOptionsDayOfWeek', scheduleOptionsDayOfWeek );
var record = component.get( 'v.record' );
component.set( 'v.scheduleSelectionsFrequency', record.scheduleFrequency );
// add the "NN." prefix to the values used for sorting
// so match the schedule options format so the selections are visually shown on the config page
// exactly one of dayOfMonth or dayOfWeek must be specified and the other must be '?',
// in our case, '?' means no selections for that value as the other field was specified
if ( !$A.util.isUndefinedOrNull( record.scheduleHourOfDay ) ) {
component.set( 'v.scheduleSelectionsHourOfDay', record.scheduleHourOfDay.split(',').map( function( hourOfDay ) { return hourOfDay.padStart( 2, '0' ) + '.' + hourOfDay; } ) );
} else {
component.set( 'v.scheduleSelectionsHourOfDay', [] );
}
if ( !$A.util.isUndefinedOrNull( record.scheduleDayOfMonth ) && record.scheduleDayOfMonth != '?' ) {
component.set( 'v.scheduleSelectionsDayOfMonth', record.scheduleDayOfMonth.split(',').map( function( dayOfMonth ) { return dayOfMonth.padStart( 2, '0' ) + '.' + dayOfMonth; } ) );
} else {
component.set( 'v.scheduleSelectionsDayOfMonth', [] );
}
if ( !$A.util.isUndefinedOrNull( record.scheduleMonthOfYear ) ) {
component.set( 'v.scheduleSelectionsMonthOfYear', record.scheduleMonthOfYear.split(',').map( function( monthOfYear ) { return monthValues.indexOf( monthOfYear ).toString().padStart( 2, '0' ) + '.' + monthOfYear; } ) );
} else {
component.set( 'v.scheduleSelectionsMonthOfYear', [] );
}
if ( !$A.util.isUndefinedOrNull( record.scheduleDayOfWeek ) && record.scheduleDayOfWeek != '?' ) {
component.set( 'v.scheduleSelectionsDayOfWeek', record.scheduleDayOfWeek.split(',').map( function( dayOfWeek ) { return weekdayValues.indexOf( dayOfWeek ).toString().padStart( 2, '0' ) + '.' + dayOfWeek; } ) );
} else {
component.set( 'v.scheduleSelectionsDayOfWeek', [] );
}
},
// ----------------------------------------------------------------------------------
handleSourceTypeChange : function( component ) {
var helper = this;
var promises = [];
var sourceType = component.get( 'v.sourceType' );
var record = component.get( 'v.record' );
if ( sourceType != 'Report' ) {
record.sourceReportID = null;
record.sourceReportColumnName = null;
component.set( 'v.sourceReport', null );
component.set( 'v.sourceReportId', null );
component.set( 'v.sourceReportFolderId', null );
component.set( 'v.sourceReportColumns', null );
component.set( 'v.sourceReportColumnName', null );
} else {
if ( helper.isEmpty( component.get( 'v.sourceReportFolders' ) ) ) {
promises.push( helper.getReportFoldersAsync( component )
.then( $A.getCallback( function( reportFolders ) {
component.set( 'v.sourceReportFolders', reportFolders );
helper.handleSourceReportFolderChange( component );
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Getting Report Folders', err, 'error' );
}))
);
}
}
if ( sourceType != 'ListView' ) {
record.sourceListViewID = null;
component.set( 'v.sourceListView', null );
component.set( 'v.sourceListViewId', null );
component.set( 'v.sourceListViewSobjectType', null );
} else {
if ( helper.isEmpty( component.get( 'v.sourceListViewSobjectTypes' ) ) ) {
promises.push( helper.getObjectNamesWithListViewsAsync( component )
.then( $A.getCallback( function( objectNames ) {
component.set( 'v.sourceListViewSobjectTypes', objectNames );
helper.handleSourceListViewSobjectTypeChange( component );
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Getting Object Names', err, 'error' );
}))
);
}
}
if ( sourceType != 'SOQL' ) {
record.sourceSoqlQuery = null;
}
component.set( 'v.record', record );
component.set( 'v.sourceTypeURL', null );
return Promise.all( promises );
},
handleSourceReportFolderChange : function( component ) {
var helper = this;
var promises = [];
var sourceType = component.get( 'v.sourceType' );
var report = component.get( 'v.sourceReport' );
var folderId = component.get( 'v.sourceReportFolderId' );
if ( sourceType == 'Report' ) {
var reportFolderId = report && report.OwnerId && report.OwnerId.substring( 0, 15 );
folderId = folderId && folderId.substring( 0, 15 );
if ( folderId != reportFolderId ) {
component.set( 'v.sourceReport', null );
component.set( 'v.sourceReportId', null );
component.set( 'v.sourceReportColumnName', null );
component.set( 'v.record.sourceReportID', null );
component.set( 'v.record.sourceReportColumnName', null );
}
promises.push( helper.getReportsByFolderAsync( component, folderId )
.then( $A.getCallback( function( reports ) {
component.set( 'v.sourceReports', reports );
return helper.handleSourceReportChange( component );
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Getting Reports By Folder', err, 'error' );
}))
);
}
return Promise.all( promises );
},
handleSourceReportChange : function( component ) {
var helper = this;
var promises = [];
var sourceType = component.get( 'v.sourceType' );
var reportId = component.get( 'v.sourceReportId' );
if ( sourceType == 'Report' ) {
if ( helper.isEmpty( reportId ) ) {
component.set( 'v.sourceTypeURL', null );
component.set( 'v.sourceReport', null );
component.set( 'v.sourceReportColumns', null );
component.set( 'v.sourceReportColumnName', null );
component.set( 'v.record.sourceReportID', null );
component.set( 'v.record.sourceReportColumnName', null );
} else {
promises.push( helper.getReportAsync( component, reportId )
.then( $A.getCallback( function( report ) {
component.set( 'v.sourceTypeURL', '/lightning/r/Report/' + report.Id + '/view' );
component.set( 'v.sourceReport', report );
component.set( 'v.record.sourceReportID', ( report.Id && report.Id.substring( 0, 15 ) ) );
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Getting Report', err, 'error' );
}))
);
promises.push( helper.getReportColumnsAsync( component, reportId )
.then( $A.getCallback( function( reportColumns ) {
component.set( 'v.sourceReportColumns', reportColumns );
var columnName = component.get( 'v.sourceReportColumnName' );
var columnFound = false;
for ( var i = 0; i < reportColumns.length; i++ ) {
if ( reportColumns[i].value == columnName ) {
columnFound = true;
break;
}
}
if ( !columnFound ) {
component.set( 'v.sourceReportColumnName', null );
component.set( 'v.record.sourceReportColumnName', null );
} else {
component.set( 'v.record.sourceReportColumnName', columnName );
}
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Getting Report Columns', err, 'error' );
}))
);
}
}
return Promise.all( promises );
},
handleSourceListViewSobjectTypeChange : function( component ) {
var helper = this;
var promises = [];
var sourceType = component.get( 'v.sourceType' );
var listView = component.get( 'v.sourceListView' );
var sobjectType = component.get( 'v.sourceListViewSobjectType' );
if ( sourceType == 'ListView' ) {
if ( !$A.util.isUndefinedOrNull( listView ) && listView.SobjectType != sobjectType ) {
component.set( 'v.sourceListViewId', null );
component.set( 'v.record.sourceListViewID', null );
}
promises.push( helper.getListViewsByObjectAsync( component, sobjectType )
.then( $A.getCallback( function( listViews ) {
component.set( 'v.sourceListViews', listViews );
return helper.handleSourceListViewChange( component );
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Getting List Views By Object', err, 'error' );
}))
);
}
return Promise.all( promises );
},
handleSourceListViewChange : function( component ) {
var helper = this;
var promises = [];
var sourceType = component.get( 'v.sourceType' );
var listViewId = component.get( 'v.sourceListViewId' );
if ( sourceType == 'ListView' ) {
if ( helper.isEmpty( listViewId ) ) {
component.set( 'v.sourceTypeURL', null );
component.set( 'v.sourceListView', null );
component.set( 'v.record.sourceListViewID', null );
} else {
promises.push( helper.getListViewAsync( component, listViewId )
.then( $A.getCallback( function( listView ) {
component.set( 'v.sourceTypeURL', '/lightning/o/' + listView.SobjectType + '/list?filterName=' + listView.Id );
component.set( 'v.sourceListView', listView );
component.set( 'v.record.sourceListViewID', ( listView.Id && listView.Id.substring( 0, 15 ) ) );
})).catch( $A.getCallback( function( err ) {
helper.toastMessage( 'Error Getting List View', err, 'error' );
}))
);
}
}
return Promise.all( promises );
},
// ----------------------------------------------------------------------------------
handleTargetTypeChange : function( component ) {
var helper = this;
var promises = [];
var targetType = component.get( 'v.targetType' );
var targetApexType = component.get( 'v.targetApexType' );
var targetActionName = component.get( 'v.targetInvocableAction' );
var record = component.get( 'v.record' );
// if true then we need to display prompt to user
// to choose an object before we can show action options
var targetTypeRequiresSobject = false;
var targetTypeRequiresAction = false;
if ( helper.isEmpty( targetType ) || targetType == 'Workflow' ) {
targetTypeRequiresSobject = false;
targetTypeRequiresAction = false;
targetApexType = null;
targetActionName = null;
record.targetSobjectType = null;
record.targetApexScript = null;
} else if ( targetType == 'Flow' ) {
targetTypeRequiresSobject = false;
targetTypeRequiresAction = true;
targetApexType = null;
//targetActionName = null;
record.targetSobjectType = null;
record.targetApexScript = null;
} else if ( targetType == 'QuickAction' ) {
targetTypeRequiresSobject = true;
targetTypeRequiresAction = true;
targetApexType = null;
//targetActionName = null;
//record.targetSobjectType = null;
record.targetApexScript = null;
} else if ( targetType == 'EmailAlert' ) {
targetTypeRequiresSobject = true;
targetTypeRequiresAction = true;
targetApexType = null;
//targetActionName = null;
//record.targetSobjectType = null;
record.targetApexScript = null;
} else if ( targetType == 'Apex' ) {
targetTypeRequiresSobject = false;
targetTypeRequiresAction = true;
record.targetSobjectType = null;
if ( targetApexType === 'Invocable' ) {
targetTypeRequiresAction = true;
record.targetApexScript = null;
} else if ( targetApexType === 'Anonymous' ) {
targetTypeRequiresAction = false;
targetActionName = null;
// provide a template as convenience
if ( $A.util.isEmpty( record.targetApexScript ) ) {
record.targetApexScript = (
'void execute( List