Skip to content

Commit

Permalink
refactor(core): reactify CRON trigger (#7020)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jammy Louie committed May 22, 2019
1 parent 64589b6 commit 2466379
Show file tree
Hide file tree
Showing 21 changed files with 1,132 additions and 624 deletions.
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>
);
}
}
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>
);
}
}
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>
);
}
}
Loading

0 comments on commit 2466379

Please sign in to comment.