Skip to content

Commit 1792ecf

Browse files
committed
Adding skipWhenAlreadyScheduled.
1 parent 668bb21 commit 1792ecf

File tree

8 files changed

+91
-25
lines changed

8 files changed

+91
-25
lines changed

force-app/main/default/classes/AsyncTest.cls

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,49 @@ private class AsyncTest implements Database.Batchable<SObject> {
727727
Assert.areEqual(10, triggers[0].NextFireTime.hour());
728728
}
729729

730+
@IsTest
731+
static void shouldFailWhenSchedulingJobWithTheSameName() {
732+
Test.startTest();
733+
try {
734+
SchedulableBuilder builder = Async.batchable(new AsyncTest())
735+
.asSchedulable()
736+
.name(TEST_SCHEDULABLE_JOB_NAME)
737+
.cronExpression(
738+
new CronBuilder()
739+
.everyXMonths(4, 3, 5, 25)
740+
);
741+
builder.schedule();
742+
builder.schedule();
743+
Assert.fail('Should throw system exception when trying to schedule the job with the same name.');
744+
} catch (Exception ex) {
745+
Assert.areEqual('System.AsyncException', ex.getTypeName());
746+
}
747+
Test.stopTest();
748+
}
749+
750+
@IsTest
751+
static void shouldSkipWhenSchedulingJobWithTheSameName() {
752+
Test.startTest();
753+
SchedulableBuilder builder = Async.queueable(new SuccessfulQueueableTest())
754+
.asSchedulable()
755+
.name(TEST_SCHEDULABLE_JOB_NAME)
756+
.cronExpression(
757+
new CronBuilder()
758+
.everyDay(10, 30)
759+
)
760+
.skipWhenAlreadyScheduled();
761+
builder.schedule();
762+
builder.schedule();
763+
Test.stopTest();
764+
765+
String nameLike = TEST_SCHEDULABLE_JOB_NAME + '%';
766+
List<CronTrigger> triggers = [SELECT CronExpression, NextFireTime FROM CronTrigger WHERE CronJobDetail.Name LIKE :nameLike];
767+
Assert.areEqual(1, triggers.size());
768+
Assert.areEqual('0 30 10 * * ? *', triggers[0].CronExpression);
769+
Assert.areEqual(30, triggers[0].NextFireTime.minute());
770+
Assert.areEqual(10, triggers[0].NextFireTime.hour());
771+
}
772+
730773
public Iterable<Account> start(Database.BatchableContext bc){
731774
// This is just a placeholder to start the batch.
732775
return new List<Account>{ new Account() };

force-app/main/default/classes/schedule/SchedulableBuilder.cls

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
public inherited sharing class SchedulableBuilder {
22
public Schedulable scheduleJob;
3+
public BatchableBuilder batchableBuilder;
4+
35
public String name;
46
public List<CronBuilder> crons = new List<CronBuilder>();
5-
public BatchableBuilder batchableBuilder;
7+
public Boolean skipWhenAlreadyScheduled = false;
68

79
public SchedulableBuilder(QueueableBuilder builder) {
810
this.scheduleJob = new QueueableSchedulable(builder);
@@ -23,20 +25,23 @@ public inherited sharing class SchedulableBuilder {
2325
}
2426

2527
public SchedulableBuilder cronExpression(String cronExpression) {
26-
this.crons.add(new CronBuilder(cronExpression));
27-
return this;
28+
return cronExpression(new CronBuilder(cronExpression));
2829
}
2930

3031
public SchedulableBuilder cronExpression(CronBuilder builder) {
31-
this.crons.add(builder);
32-
return this;
32+
return cronExpression(new List<CronBuilder> {builder});
3333
}
3434

3535
public SchedulableBuilder cronExpression(List<CronBuilder> builders) {
3636
this.crons.addAll(builders);
3737
return this;
3838
}
3939

40+
public SchedulableBuilder skipWhenAlreadyScheduled() {
41+
this.skipWhenAlreadyScheduled = true;
42+
return this;
43+
}
44+
4045
public List<Async.AsyncResult> schedule() {
4146
return SchedulableManager.get().schedule(this);
4247
}

force-app/main/default/classes/schedule/SchedulableManager.cls

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ public inherited sharing class SchedulableManager {
3131
if ((builder.crons != null && !builder.crons.isEmpty()) && builder.batchableBuilder?.minutesFromNow != null) {
3232
throw new IllegalArgumentException(ERROR_MESSAGE_CRON_AND_MINUTES_SET);
3333
}
34+
35+
if (shouldSkipWhenAlreadyScheduled(builder)) {
36+
return null;
37+
}
3438

3539
List<Async.AsyncResult> results = new List<Async.AsyncResult>();
3640
if (builder.batchableBuilder?.minutesFromNow != null) {
@@ -61,6 +65,10 @@ public inherited sharing class SchedulableManager {
6165
return results;
6266
}
6367

68+
private Boolean shouldSkipWhenAlreadyScheduled(SchedulableBuilder builder) {
69+
return builder.skipWhenAlreadyScheduled && ![SELECT Id FROM CronJobDetail WHERE Name = :builder.name].isEmpty();
70+
}
71+
6472
public static SchedulableManager get() {
6573
if (instance == null) {
6674
instance = new SchedulableManager();

website/.vitepress/config.mts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default defineConfig({
1414
sidebar: [
1515
{
1616
text: 'Introduction',
17-
collapsed: true,
17+
collapsed: false,
1818
items: [
1919
{ text: 'Getting Started', link: '/getting-started' },
2020
{ text: 'Installation', link: '/introduction/installation' }
@@ -28,6 +28,13 @@ export default defineConfig({
2828
{ text: 'Batchable', link: '/api/batchable' },
2929
{ text: 'Schedulable', link: '/api/schedulable' }
3030
]
31+
},
32+
{
33+
text: 'Explanations',
34+
collapsed: false,
35+
items: [
36+
{ text: 'Initial Scheduled Job', link: '/explanations/initial-scheduled-queuable-batch-job' }
37+
]
3138
}
3239
],
3340
footer: {

website/api/schedulable.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Schedulable job = new MySchedulableJob();
99
List<Async.AsyncResult> results = Async.schedulable(job)
1010
.name('Daily Processing Job')
1111
.cronExpression('0 0 2 * * ? *')
12+
.skipWhenAlreadyScheduled()
1213
.schedule();
1314
System.debug('Scheduled job results: ' + results);
1415
```
@@ -27,6 +28,7 @@ The following are methods for using Async with Schedulable jobs:
2728
- [`cronExpression(String cronExpression)`](#cronexpression)
2829
- [`cronExpression(CronBuilder builder)`](#cronexpression-1)
2930
- [`cronExpression(List<CronBuilder> builders)`](#cronexpression-2)
31+
- [`skipWhenAlreadyScheduled()`]()
3032

3133
[**Build - Cron Expression**](#build---cron-expression)
3234

@@ -161,6 +163,23 @@ Async.schedulable(new MySchedulableJob())
161163
.cronExpression(crons);
162164
```
163165

166+
#### skipWhenAlreadyScheduled
167+
168+
If set, the job will not be scheduled if it is already scheduled with the same name. This prevents throwing the `System.AsyncException`.
169+
170+
**Signature**
171+
172+
```apex
173+
SchedulableBuilder skipWhenAlreadyScheduled();
174+
```
175+
176+
**Example**
177+
178+
```apex
179+
Async.schedulable(new MySchedulableJob())
180+
.skipWhenAlreadyScheduled();
181+
```
182+
164183
### Build - Cron Expression
165184

166185
#### second
File renamed without changes.

website/getting-started.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ Async Lib is a powerful Salesforce Apex framework that provides an elegant solut
5757
- **⚙️ Configuration-Driven**: Control job behavior through custom metadata without code changes
5858
- **🔗 Support Finalizers**: Execute cleanup logic after job completion with full context
5959

60-
```
61-
6260
## Core Concepts
6361

6462
### 1. QueueableJob Base Class
@@ -94,6 +92,7 @@ Async.batchable(new MyBatchJob())
9492
Async.schedulable(new MySchedulableJob())
9593
.name('Daily Cleanup')
9694
.cronExpression('0 0 2 * * ? *')
95+
.skipWhenAlreadyScheduled()
9796
.schedule();
9897
```
9998

@@ -213,7 +212,7 @@ Now that you understand the basics:
213212
2. **[Batchable API](/api/batchable.md)** - Detailed information on using Batchable jobs
214213
3. **[Schedulable API](/api/schedulable.md)** - Detailed information on using Schedulable jobs
215214
2. **Read the Blog Post** - Check out the detailed explanation: [Apex Queueable Processing Framework](https://blog.beyondthecloud.dev/blog/apex-queueable-processing-framework)
216-
3. **[Initial Scheduled Queueable Batch Job Explanation](/initial-scheduled-queuable-batch-job.md)** - Learn why this job is important for framework to function properly.
215+
3. **[Initial Scheduled Queueable Batch Job Explanation](/explanations/initial-scheduled-queuable-batch-job.md)** - Learn why this job is important for framework to function properly.
217216

218217
## Quick Tips
219218

website/introduction/installation.md

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,7 @@
22
outline: deep
33
---
44

5-
# Installation <Badge type="tip" text="v1.0.0" />
6-
7-
## Install via Package
8-
9-
Install the SOQL Lib unmanaged package to your Salesforce environment:
10-
11-
12-
`/packaging/installPackage.apexp?p0=04tP6000001w95Z`
13-
14-
<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04tP6000001w95Z" target="_blank">
15-
<p>Install on Sandbox</p>
16-
</a>
17-
18-
<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04tP6000001w95Z" target="_blank">
19-
<p>Install on Production</p>
20-
</a>
5+
# Installation <Badge type="tip" text="v2.0.0" />
216

227
## Deploy via Button
238

0 commit comments

Comments
 (0)