1 | import _ from 'lodash';
|
2 | import Time from './time';
|
3 | import { OPERATORS, TYPE_ANY, TYPES } from './constants';
|
4 |
|
5 | const DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
6 |
|
7 | const MONTH = [
|
8 | 'Jan',
|
9 | 'Feb',
|
10 | 'Mar',
|
11 | 'Apr',
|
12 | 'May',
|
13 | 'Jun',
|
14 | 'Jul',
|
15 | 'Aug',
|
16 | 'Sep',
|
17 | 'Oct',
|
18 | 'Nov',
|
19 | 'Dec'
|
20 | ];
|
21 |
|
22 | const PROPERTY_FORMATTER = {
|
23 | [TYPE_ANY]: (value) => value,
|
24 | [TYPES.continuous]: (number) => `${Math.round(number * 100) / 100}`,
|
25 | [TYPES.time_of_day]: (time) => {
|
26 | const _time = time instanceof Time ? time.time_of_day : time;
|
27 | const hours = Math.floor(_time);
|
28 | const hoursStr = _.padStart(hours, 2, '0');
|
29 | const decMinutes = Math.round((_time - hours) * 60 * 100) / 100;
|
30 | const minutes = Math.floor(decMinutes);
|
31 | const minutesStr = _.padStart(minutes, 2, '0');
|
32 | const seconds = Math.round((decMinutes - minutes) * 60);
|
33 | const secondsStr = _.padStart(seconds, 2, '0');
|
34 |
|
35 | if (seconds > 0) {
|
36 | return `${hoursStr}:${minutesStr}:${secondsStr}`;
|
37 | }
|
38 | else {
|
39 | return `${hoursStr}:${minutesStr}`;
|
40 | }
|
41 | },
|
42 | [TYPES.day_of_week]: (day) => {
|
43 | const _day = day instanceof Time ? day.day_of_week : day;
|
44 | return DAYS[_day];
|
45 | },
|
46 | [TYPES.day_of_month]: (day) => {
|
47 | const _day = day instanceof Time ? day.day_of_month : day;
|
48 | return _.padStart(_day, 2, '0');
|
49 | },
|
50 |
|
51 | [TYPES.month_of_year]: (month) => {
|
52 | const _month = month instanceof Time ? month.month_of_year : month;
|
53 | return MONTH[_month - 1];
|
54 | }
|
55 | };
|
56 |
|
57 | export function formatProperty(type, value = undefined) {
|
58 | const formatter = PROPERTY_FORMATTER[type] || PROPERTY_FORMATTER[TYPE_ANY];
|
59 | const extendedFormatter = (value) => {
|
60 |
|
61 | if (_.isNull(value)) {
|
62 | return 'null';
|
63 | }
|
64 |
|
65 | else if (_.isPlainObject(value) && _.isEmpty(value)) {
|
66 | return 'N/A';
|
67 | }
|
68 | return formatter(value);
|
69 | };
|
70 | if (!_.isUndefined(value)) {
|
71 | return extendedFormatter(value);
|
72 | }
|
73 | return extendedFormatter;
|
74 | }
|
75 |
|
76 | const FORMATTER_FROM_DECISION_RULE = {
|
77 | [OPERATORS.IS]: {
|
78 | [TYPE_ANY]: ({ property, operand, operandFormatter }) => {
|
79 | if (property) {
|
80 | return `'${property}' is ${operandFormatter(operand)}`;
|
81 | }
|
82 | return `is ${operandFormatter(operand)}`;
|
83 | }
|
84 | },
|
85 | [OPERATORS.IN]: {
|
86 | [TYPE_ANY]: ({ property, operand, operandFormatter }) => {
|
87 | if (property) {
|
88 | return `'${property}' in [${operandFormatter(
|
89 | operand[0]
|
90 | )}, ${operandFormatter(operand[1])}[`;
|
91 | }
|
92 | return `[${operandFormatter(operand[0])}, ${operandFormatter(
|
93 | operand[1]
|
94 | )}[`;
|
95 | },
|
96 | [TYPES.day_of_week]: ({ property, operand, operandFormatter }) => {
|
97 | const day_from = Math.floor(operand[0]);
|
98 | const day_to = Math.floor(operand[1]);
|
99 |
|
100 | if (day_to - day_from == 1 || (day_from == 6 && day_to == 0)) {
|
101 | if (property) {
|
102 | return `'${property}' is ${operandFormatter(day_from)}`;
|
103 | }
|
104 | return operandFormatter(day_from);
|
105 | }
|
106 | else {
|
107 | if (property) {
|
108 | return `'${property}' from ${operandFormatter(
|
109 | day_from
|
110 | )} to ${operandFormatter((7 + day_to - 1) % 7)}`;
|
111 | }
|
112 | return `${operandFormatter(day_from)} to ${operandFormatter(
|
113 | (7 + day_to - 1) % 7
|
114 | )}`;
|
115 | }
|
116 | },
|
117 | [TYPES.month_of_year]: ({ property, operand, operandFormatter }) => {
|
118 | const month_from = Math.floor(operand[0]);
|
119 | const month_to = Math.floor(operand[1]);
|
120 | if (month_to - month_from == 1 || (month_from == 12 && month_to == 1)) {
|
121 |
|
122 | if (property) {
|
123 | return `'${property}' is ${operandFormatter(month_from)}`;
|
124 | }
|
125 | return operandFormatter(month_from);
|
126 | }
|
127 | else if (month_to == 1) {
|
128 |
|
129 | if (property) {
|
130 | return `'${property}' from ${operandFormatter(
|
131 | month_from
|
132 | )} to ${operandFormatter(12)}`;
|
133 | }
|
134 | return `${operandFormatter(month_from)} to ${operandFormatter(12)}`;
|
135 | }
|
136 | else {
|
137 | if (property) {
|
138 | return `'${property}' from ${operandFormatter(
|
139 | month_from
|
140 | )} to ${operandFormatter(month_to - 1)}`;
|
141 | }
|
142 | return `${operandFormatter(month_from)} to ${operandFormatter(
|
143 | month_to - 1
|
144 | )}`;
|
145 | }
|
146 | }
|
147 | },
|
148 | [OPERATORS.GTE]: {
|
149 | [TYPE_ANY]: ({ property, operand, operandFormatter }) => {
|
150 | if (property) {
|
151 | return `'${property}' >= ${operandFormatter(operand)}`;
|
152 | }
|
153 | return `>= ${operandFormatter(operand)}`;
|
154 | }
|
155 | },
|
156 | [OPERATORS.LT]: {
|
157 | [TYPE_ANY]: ({ property, operand, operandFormatter }) => {
|
158 | if (property) {
|
159 | return `'${property}' < ${operandFormatter(operand)}`;
|
160 | }
|
161 | return `< ${operandFormatter(operand)}`;
|
162 | }
|
163 | }
|
164 | };
|
165 |
|
166 | export function formatDecisionRules(decisionRules) {
|
167 | return decisionRules
|
168 | .map(({ property, type, operand, operator }) => {
|
169 | const operatorFormatters = FORMATTER_FROM_DECISION_RULE[operator];
|
170 | if (!operatorFormatters) {
|
171 | throw new Error(
|
172 | `Unable to format the given decision rule: unknown operator '${operator}'.`
|
173 | );
|
174 | }
|
175 | const formatter =
|
176 | operatorFormatters[type] || operatorFormatters[TYPE_ANY];
|
177 | const operandFormatter = formatProperty(type || TYPE_ANY);
|
178 | return formatter({ property, type, operator, operandFormatter, operand });
|
179 | })
|
180 | .join(' and ');
|
181 | }
|