-
Notifications
You must be signed in to change notification settings - Fork 900
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(core): reactify CRON trigger (#7020)
- Loading branch information
Jammy Louie
committed
May 22, 2019
1 parent
64589b6
commit 2466379
Showing
21 changed files
with
1,132 additions
and
624 deletions.
There are no files selected for viewing
126 changes: 126 additions & 0 deletions
126
app/scripts/modules/core/src/pipeline/config/triggers/cron/CronAdvance.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import * as React from 'react'; | ||
|
||
import { Observable, Subject } from 'rxjs'; | ||
|
||
import { HelpField } from 'core/help'; | ||
import { CronValidatorService } from './cronValidator.service'; | ||
import { ICronTriggerConfigProps } from './cronConfig'; | ||
|
||
export interface ICronAdvanceState { | ||
description?: string; | ||
errorMessage?: string; | ||
} | ||
|
||
export class CronAdvance extends React.Component<ICronTriggerConfigProps, ICronAdvanceState> { | ||
private destroy$ = new Subject(); | ||
|
||
constructor(props: ICronTriggerConfigProps) { | ||
super(props); | ||
this.state = { | ||
description: '', | ||
errorMessage: '', | ||
}; | ||
} | ||
|
||
public componentDidMount(): void { | ||
this.validateCronExpression(this.props.trigger.cronExpression); | ||
} | ||
|
||
public componentWillUnmount(): void { | ||
this.destroy$.next(); | ||
} | ||
|
||
private validateCronExpression = (cronExpression: string) => { | ||
Observable.fromPromise(CronValidatorService.validate(cronExpression)) | ||
.takeUntil(this.destroy$) | ||
.subscribe( | ||
(result: { valid: boolean; message?: string; description?: string }) => { | ||
if (result.valid) { | ||
this.setState({ | ||
description: result.description | ||
? result.description.charAt(0).toUpperCase() + result.description.slice(1) | ||
: '', | ||
errorMessage: '', | ||
}); | ||
} else { | ||
this.setState({ | ||
description: '', | ||
errorMessage: result && result.message ? result.message : 'Error validating CRON expression', | ||
}); | ||
} | ||
}, | ||
(result: { valid: boolean; message?: string; description?: string }) => { | ||
this.setState({ | ||
description: '', | ||
errorMessage: result && result.message ? result.message : 'Error validating CRON expression', | ||
}); | ||
}, | ||
); | ||
}; | ||
|
||
private onUpdateTrigger = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const cronExpression = event.target.value.replace(/\s\s+/g, ' '); | ||
this.props.triggerUpdated({ | ||
...this.props.trigger, | ||
cronExpression, | ||
}); | ||
this.validateCronExpression(cronExpression); | ||
}; | ||
|
||
public render() { | ||
const { cronExpression } = this.props.trigger; | ||
const { description, errorMessage } = this.state; | ||
return ( | ||
<div> | ||
<div className="row"> | ||
<div className="col-md-12"> | ||
<strong>Expression</strong> | ||
<HelpField id="pipeline.config.cron.expression" />{' '} | ||
<input | ||
type="text" | ||
className="form-control input-sm" | ||
onChange={this.onUpdateTrigger} | ||
required={true} | ||
value={cronExpression} | ||
/> | ||
</div> | ||
</div> | ||
<div className="row"> | ||
<div className="col-md-12"> | ||
<p> | ||
More details about how to create these expressions can be found{' '} | ||
<a | ||
href="http://www.quartz-scheduler.org/documentation/2.3.1-SNAPSHOT/tutorials/tutorial-lesson-06.html" | ||
target="_blank" | ||
> | ||
here | ||
</a>{' '} | ||
and{' '} | ||
<a href="https://www.freeformatter.com/cron-expression-generator-quartz.html" target="_blank"> | ||
here | ||
</a> | ||
. | ||
</p> | ||
</div> | ||
</div> | ||
{description && !errorMessage && ( | ||
<div className="row"> | ||
<div className="col-md-12"> | ||
<p> | ||
<strong>{description}</strong> | ||
</p> | ||
</div> | ||
</div> | ||
)} | ||
{errorMessage && ( | ||
<div className="row slide-in"> | ||
<div className="col-md-12 error-message"> | ||
<p>This trigger will NEVER fire.</p> | ||
{errorMessage} | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
} | ||
} |
171 changes: 171 additions & 0 deletions
171
app/scripts/modules/core/src/pipeline/config/triggers/cron/CronDaily.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import * as React from 'react'; | ||
import Select, { Option } from 'react-select'; | ||
|
||
import { SystemTimezone } from 'core/utils/SystemTimezone'; | ||
import { HOURS, MINUTES } from './cronSelectOptions'; | ||
import { ICronTriggerConfigProps } from './cronConfig'; | ||
|
||
export interface ICronDailyState { | ||
subTab: string; | ||
everyDaysDays: string; | ||
hour: string; | ||
minute: string; | ||
} | ||
|
||
export class CronDaily extends React.Component<ICronTriggerConfigProps, ICronDailyState> { | ||
constructor(props: ICronTriggerConfigProps) { | ||
super(props); | ||
this.state = { | ||
subTab: 'everyDays', | ||
everyDaysDays: '1', | ||
hour: '0', | ||
minute: '0', | ||
}; | ||
} | ||
|
||
public componentDidMount() { | ||
const everyDaysRegex = /0 (\d+) (\d+) 1\/(\d+) \* \? \*/g; | ||
const everyWeekDayRegex = /0 (\d+) (\d+) \? \* MON\-FRI \*/g; | ||
const everyDaysMatches = everyDaysRegex.exec(this.props.trigger.cronExpression); | ||
const everyWeekDayMatches = everyWeekDayRegex.exec(this.props.trigger.cronExpression); | ||
if (everyDaysMatches) { | ||
this.setState({ | ||
subTab: 'everyDays', | ||
minute: everyDaysMatches[1], | ||
hour: everyDaysMatches[2], | ||
everyDaysDays: everyDaysMatches[3], | ||
}); | ||
} else if (everyWeekDayMatches) { | ||
this.setState({ | ||
subTab: 'everyWeekDay', | ||
minute: everyWeekDayMatches[1], | ||
hour: everyWeekDayMatches[2], | ||
}); | ||
} else { | ||
this.props.triggerUpdated({ | ||
...this.props.trigger, | ||
cronExpression: '0 0 0 1/' + this.state.everyDaysDays + ' * ? *', | ||
}); | ||
} | ||
} | ||
|
||
private onUpdateSubTabTrigger = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const subTab = event.target.value; | ||
this.setState({ subTab }); | ||
if (subTab === 'everyDays') { | ||
this.props.triggerUpdated({ | ||
...this.props.trigger, | ||
cronExpression: '0 0 0 1/' + this.state.everyDaysDays + ' * ? *', | ||
}); | ||
} else if (subTab === 'everyWeekDay') { | ||
this.props.triggerUpdated({ | ||
...this.props.trigger, | ||
cronExpression: '0 ' + this.state.minute + ' ' + this.state.hour + ' ? * MON-FRI *', | ||
}); | ||
} | ||
}; | ||
|
||
private onUpdateEveryDaysDaysTrigger = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const everyDaysDays = event.target.value; | ||
this.setState({ everyDaysDays }); | ||
this.props.triggerUpdated({ | ||
...this.props.trigger, | ||
cronExpression: '0 0 0 1/' + everyDaysDays + ' * ? *', | ||
}); | ||
}; | ||
|
||
private onUpdateHourTrigger = (option: Option<string>) => { | ||
const hour = option.value; | ||
const { everyDaysDays, minute, subTab } = this.state; | ||
this.setState({ hour }); | ||
if (subTab === 'everyDays') { | ||
this.props.triggerUpdated({ | ||
...this.props.trigger, | ||
cronExpression: '0 ' + minute + ' ' + hour + ' 1/' + everyDaysDays + ' * ? *', | ||
}); | ||
} else if (subTab === 'everyWeekDay') { | ||
this.props.triggerUpdated({ | ||
...this.props.trigger, | ||
cronExpression: '0 ' + minute + ' ' + hour + ' ? * MON-FRI *', | ||
}); | ||
} | ||
}; | ||
|
||
private onUpdateMinuteTrigger = (option: Option<string>) => { | ||
const minute = option.value; | ||
const { everyDaysDays, hour, subTab } = this.state; | ||
this.setState({ minute }); | ||
if (subTab === 'everyDays') { | ||
this.props.triggerUpdated({ | ||
...this.props.trigger, | ||
cronExpression: '0 ' + minute + ' ' + hour + ' 1/' + everyDaysDays + ' * ? *', | ||
}); | ||
} else if (subTab === 'everyWeekDay') { | ||
this.props.triggerUpdated({ | ||
...this.props.trigger, | ||
cronExpression: '0 ' + minute + ' ' + hour + ' ? * MON-FRI *', | ||
}); | ||
} | ||
}; | ||
|
||
public render() { | ||
const { everyDaysDays, hour, minute, subTab } = this.state; | ||
return ( | ||
<div> | ||
<div className="row"> | ||
<div className="col-md-12"> | ||
<input | ||
type="radio" | ||
value="everyDays" | ||
onChange={this.onUpdateSubTabTrigger} | ||
checked={subTab === 'everyDays'} | ||
/> | ||
<span>Every </span> | ||
<input | ||
className="form-control input-sm" | ||
type="number" | ||
min="1" | ||
max="31" | ||
disabled={subTab !== 'everyDays'} | ||
onChange={this.onUpdateEveryDaysDaysTrigger} | ||
required={subTab === 'everyDays'} | ||
value={everyDaysDays} | ||
/> | ||
<span> day(s)</span> | ||
</div> | ||
</div> | ||
<div className="row"> | ||
<div className="col-md-12"> | ||
<input | ||
type="radio" | ||
value="everyWeekDay" | ||
onChange={this.onUpdateSubTabTrigger} | ||
checked={subTab === 'everyWeekDay'} | ||
/> | ||
Every week day | ||
</div> | ||
</div> | ||
<div className="row"> | ||
<div className="col-md-12"> | ||
<span>Start time </span> | ||
<Select | ||
className="visible-xs-inline-block visible-sm-inline-block visible-md-inline-block visible-lg-inline-block vertical-align-select" | ||
clearable={false} | ||
value={hour} | ||
options={HOURS} | ||
onChange={this.onUpdateHourTrigger} | ||
/>{' '} | ||
<Select | ||
className="visible-xs-inline-block visible-sm-inline-block visible-md-inline-block visible-lg-inline-block vertical-align-select" | ||
clearable={false} | ||
value={minute} | ||
options={MINUTES} | ||
onChange={this.onUpdateMinuteTrigger} | ||
/>{' '} | ||
<SystemTimezone /> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
} |
84 changes: 84 additions & 0 deletions
84
app/scripts/modules/core/src/pipeline/config/triggers/cron/CronHourly.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import * as React from 'react'; | ||
|
||
import { ICronTriggerConfigProps } from './cronConfig'; | ||
|
||
export interface ICronHourlyState { | ||
hours?: string; | ||
minutes?: string; | ||
} | ||
|
||
export class CronHourly extends React.Component<ICronTriggerConfigProps, ICronHourlyState> { | ||
constructor(props: ICronTriggerConfigProps) { | ||
super(props); | ||
this.state = { | ||
hours: '1', | ||
minutes: '1', | ||
}; | ||
} | ||
|
||
public componentDidMount() { | ||
const regex = /0 (\d+) 0\/(\d+) 1\/1 \* \? \*/g; | ||
const matches = regex.exec(this.props.trigger.cronExpression); | ||
if (matches) { | ||
this.setState({ | ||
minutes: matches[1], | ||
hours: matches[2], | ||
}); | ||
} else { | ||
this.props.triggerUpdated({ | ||
...this.props.trigger, | ||
cronExpression: '0 ' + this.state.minutes + ' 0/' + this.state.hours + ' 1/1 * ? *', | ||
}); | ||
} | ||
} | ||
|
||
private onUpdateHourlyTrigger = (hours: string) => { | ||
this.setState({ hours }); | ||
const { minutes } = this.state; | ||
this.props.triggerUpdated({ | ||
...this.props.trigger, | ||
cronExpression: '0 ' + minutes + ' 0/' + hours + ' 1/1 * ? *', | ||
}); | ||
}; | ||
|
||
private onUpdateMinuteTrigger = (minutes: string) => { | ||
this.setState({ minutes }); | ||
const { hours } = this.state; | ||
this.props.triggerUpdated({ | ||
...this.props.trigger, | ||
cronExpression: '0 ' + minutes + ' 0/' + hours + ' 1/1 * ? *', | ||
}); | ||
}; | ||
|
||
public render() { | ||
const { hours, minutes } = this.state; | ||
return ( | ||
<div> | ||
<div className="row"> | ||
<div className="col-md-12"> | ||
<span>Every </span> | ||
<input | ||
className="form-control input-sm" | ||
type="number" | ||
min="1" | ||
max="23" | ||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => this.onUpdateHourlyTrigger(event.target.value)} | ||
required={true} | ||
value={hours} | ||
/> | ||
<span> hour(s) on minute </span> | ||
<input | ||
className="form-control input-sm" | ||
type="number" | ||
min="0" | ||
max="59" | ||
required={true} | ||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => this.onUpdateMinuteTrigger(event.target.value)} | ||
value={minutes} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
} |
Oops, something went wrong.