Skip to content

Commit 65581cb

Browse files
ZabilsyaZabilsya
andauthored
[DOP-22993] add transfer schedule (#61)
* [DOP-22993] add transfer schedule * [DOP-22993] rewrite cronService --------- Co-authored-by: Zabilsya <kvcherniko@mts.ru>
1 parent a8967b5 commit 65581cb

File tree

27 files changed

+605
-9
lines changed

27 files changed

+605
-9
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"clsx": "2.1.1",
2626
"dayjs": "1.11.13",
2727
"dotenv-webpack": "8.1.0",
28+
"rc-picker": "4.9.2",
2829
"react": "18.2.0",
2930
"react-dom": "18.2.0",
3031
"react-error-boundary": "4.0.13",

src/features/transfer/MutateTransferForm/components/TransferSchedule/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Form, Input, Switch } from 'antd';
1+
import { CronSelect } from '@shared/ui';
2+
import { Form, Switch } from 'antd';
23
import React, { useState } from 'react';
34

45
export const TransferSchedule = () => {
@@ -16,7 +17,7 @@ export const TransferSchedule = () => {
1617
</Form.Item>
1718
{isScheduled && (
1819
<Form.Item label="Schedule" name="schedule" rules={[{ required: true }]}>
19-
<Input size="large" />
20+
<CronSelect />
2021
</Form.Item>
2122
)}
2223
</>

src/features/transfer/TransferDetailInfo/index.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { Descriptions } from 'antd';
33
import { Link } from 'react-router-dom';
4+
import { CronService } from '@shared/services';
45

56
import { TransferDetailInfoProps } from './types';
67
import classes from './styles.module.less';
@@ -32,12 +33,11 @@ export const TransferDetailInfo = ({
3233
<Descriptions.Item label="Queue" span={3}>
3334
<Link to={`/queues/${queue.id}`}>{queue.name}</Link>
3435
</Descriptions.Item>
35-
<Descriptions.Item label="Is scheduled" span={3}>
36-
{transfer.is_scheduled ? 'Yes' : 'No'}
37-
</Descriptions.Item>
38-
<Descriptions.Item label="Schedule" span={3}>
39-
{transfer.schedule}
40-
</Descriptions.Item>
36+
{transfer.is_scheduled && (
37+
<Descriptions.Item label="Schedule" span={3}>
38+
{new CronService(transfer.schedule).getSchedule()}
39+
</Descriptions.Item>
40+
)}
4141
<Descriptions.Item label="Strategy params" span={3}>
4242
{transfer.strategy_params.type}
4343
</Descriptions.Item>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { CronSegmentKey, CronSegmentValue, DayOfWeekName } from './types';
2+
3+
export const CRON_VALUE_DEFAULT = new Map<CronSegmentKey, CronSegmentValue>([
4+
['minute', new Date().getMinutes()],
5+
['hour', new Date().getHours()],
6+
['date', null],
7+
['day', null],
8+
]);
9+
10+
export const DAYS_OF_WEEK = Object.values(DayOfWeekName);
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { getOrdinalNumber } from '@shared/utils';
2+
3+
import { CRON_VALUE_DEFAULT, DAYS_OF_WEEK } from './constants';
4+
import { CronSegmentKey, CronSegmentValue, Period } from './types';
5+
6+
/** Class for convenient handling cron settings */
7+
export class CronService {
8+
private initialValueLength = 5;
9+
10+
private period: Period;
11+
12+
private value: Map<CronSegmentKey, CronSegmentValue>;
13+
14+
constructor(initialValue?: string) {
15+
this.value = this.transformInitialValueToMap(initialValue);
16+
this.period = this.initPeriod();
17+
}
18+
19+
private transformInitialValueToMap(initialValue?: string) {
20+
const splittedValue = initialValue?.split(' ');
21+
if (splittedValue?.length !== this.initialValueLength) {
22+
return CRON_VALUE_DEFAULT;
23+
}
24+
25+
const cronValue = splittedValue.map((segment) => {
26+
const parsedValue = parseInt(segment);
27+
if (Number.isInteger(parsedValue)) {
28+
return parsedValue;
29+
}
30+
return null;
31+
});
32+
33+
return new Map<CronSegmentKey, CronSegmentValue>([
34+
['minute', cronValue[0]],
35+
['hour', cronValue[1]],
36+
['date', cronValue[2]],
37+
['day', cronValue[4]],
38+
]);
39+
}
40+
41+
private initPeriod() {
42+
if (this.getMonthDay() === null && this.getWeekDay() === null) {
43+
return Period.DAY;
44+
}
45+
if (this.getMonthDay()) {
46+
return Period.MONTH;
47+
}
48+
return Period.WEEK;
49+
}
50+
51+
getPeriod() {
52+
return this.period;
53+
}
54+
55+
getMinute(): number {
56+
return this.value.get('minute')!;
57+
}
58+
59+
getHour(): number {
60+
return this.value.get('hour')!;
61+
}
62+
63+
getTime() {
64+
return `${this.getHour()}:${this.getMinute()}`;
65+
}
66+
67+
getMonthDay(): CronSegmentValue {
68+
return this.value.get('date') ?? null;
69+
}
70+
71+
getWeekDay(): CronSegmentValue {
72+
return this.value.get('day') ?? null;
73+
}
74+
75+
setPeriod(period: Period) {
76+
this.period = period;
77+
switch (period) {
78+
case Period.DAY:
79+
this.setMonthDay(null);
80+
this.setWeekDay(null);
81+
break;
82+
case Period.WEEK:
83+
this.setWeekDay(new Date().getDay());
84+
this.setMonthDay(null);
85+
break;
86+
case Period.MONTH:
87+
this.setWeekDay(null);
88+
this.setMonthDay(new Date().getDate());
89+
}
90+
}
91+
92+
setMinute(value: number) {
93+
if (value < 0 || value > 59) {
94+
throw new Error('Invalid value');
95+
}
96+
this.value.set('minute', value);
97+
}
98+
99+
setHour(value: number) {
100+
if (value < 0 || value > 23) {
101+
throw new Error('Invalid value');
102+
}
103+
this.value.set('hour', value);
104+
}
105+
106+
setTime(hour?: number, minute?: number) {
107+
this.setHour(hour ?? new Date().getHours());
108+
this.setMinute(minute ?? new Date().getMinutes());
109+
}
110+
111+
setMonthDay(value: CronSegmentValue) {
112+
if (value === null) {
113+
this.value.set('date', null);
114+
return;
115+
}
116+
if (value < 1 || value > 31) {
117+
throw new Error('Invalid value');
118+
}
119+
this.value.set('date', value);
120+
}
121+
122+
setWeekDay(value: CronSegmentValue) {
123+
if (value === null) {
124+
this.value.set('day', null);
125+
return;
126+
}
127+
if (value < 0 || value > 6) {
128+
throw new Error('Invalid value');
129+
}
130+
this.value.set('day', value);
131+
}
132+
133+
toString() {
134+
const minute = this.getMinute();
135+
const hour = this.getHour();
136+
const date = this.getMonthDay() ?? '*';
137+
const day = this.getWeekDay() ?? '*';
138+
return `${minute} ${hour} ${date} * ${day}`;
139+
}
140+
141+
getSchedule() {
142+
const time = this.getTime();
143+
const day = this.getWeekDay();
144+
const date = this.getMonthDay();
145+
146+
let schedule = `Every ${this.period} `;
147+
148+
if (day !== null) {
149+
schedule += `on ${DAYS_OF_WEEK[day]} `;
150+
} else if (date) {
151+
schedule += `${getOrdinalNumber(date)} `;
152+
}
153+
154+
schedule += `at ${time}`;
155+
156+
return schedule;
157+
}
158+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './cronService';
2+
export * from './types';
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export type CronSegmentValue = number | null;
2+
3+
export type CronSegmentKey = 'date' | 'day' | 'hour' | 'minute';
4+
5+
export enum Period {
6+
DAY = 'day',
7+
WEEK = 'week',
8+
MONTH = 'month',
9+
}
10+
11+
export enum DayOfWeek {
12+
SUNDAY,
13+
MONDAY,
14+
TUESDAY,
15+
WEDNESDAY,
16+
THURSDAY,
17+
FRIDAY,
18+
SATURDAY,
19+
}
20+
21+
export enum DayOfWeekName {
22+
SUNDAY = 'Sunday',
23+
MONDAY = 'Monday',
24+
TUESDAY = 'Tuesday',
25+
WEDNESDAY = 'Wednesday',
26+
THURSDAY = 'Thursday',
27+
FRIDAY = 'Friday',
28+
SATURDAY = 'Saturday',
29+
}

src/shared/services/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './cronService';

src/shared/ui/Calendar/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Dayjs } from 'dayjs';
2+
import dayjsGenerateConfig from 'rc-picker/lib/generate/dayjs';
3+
import generateCalendar from 'antd/es/calendar/generateCalendar';
4+
5+
export const Calendar = generateCalendar<Dayjs>(dayjsGenerateConfig);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react';
2+
import { Select } from 'antd';
3+
import { getOrdinalNumber } from '@shared/utils';
4+
import { Period } from '@shared/services';
5+
6+
import classes from '../../styles.module.less';
7+
import { DAYS_OF_MONTH_SELECT_OPTIONS, DAYS_OF_WEEK_SELECT_OPTIONS } from '../../constants';
8+
9+
import { DynamicSelectProps } from './types';
10+
11+
export const DynamicSelect = ({ period, weekDay, monthDay, onChangeWeekDay, onChangeMonthDay }: DynamicSelectProps) => {
12+
switch (period) {
13+
case Period.WEEK:
14+
return (
15+
<Select
16+
className={classes.day}
17+
size="large"
18+
onChange={onChangeWeekDay}
19+
options={DAYS_OF_WEEK_SELECT_OPTIONS}
20+
value={weekDay}
21+
/>
22+
);
23+
case Period.MONTH:
24+
return (
25+
<div className={classes.month}>
26+
<Select
27+
className={classes.date}
28+
size="large"
29+
onChange={onChangeMonthDay}
30+
options={DAYS_OF_MONTH_SELECT_OPTIONS}
31+
value={monthDay}
32+
/>
33+
<span>{getOrdinalNumber(monthDay!, true)}</span>
34+
</div>
35+
);
36+
default:
37+
return null;
38+
}
39+
};

0 commit comments

Comments
 (0)