@truedat/dq 4.53.6 → 4.53.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.53.7] 2022-10-14
4
+
5
+ ### Added
6
+
7
+ - [TD-3087] Add OR/AND in implementations validations
8
+
3
9
  ## [4.53.6] 2022-10-13
4
10
 
5
11
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dq",
3
- "version": "4.53.6",
3
+ "version": "4.53.7",
4
4
  "description": "Truedat Web Data Quality Module",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -34,7 +34,7 @@
34
34
  "@testing-library/jest-dom": "^5.16.4",
35
35
  "@testing-library/react": "^12.0.0",
36
36
  "@testing-library/user-event": "^13.2.1",
37
- "@truedat/test": "4.53.6",
37
+ "@truedat/test": "4.53.7",
38
38
  "babel-jest": "^28.1.0",
39
39
  "babel-plugin-dynamic-import-node": "^2.3.3",
40
40
  "babel-plugin-lodash": "^3.3.4",
@@ -93,8 +93,8 @@
93
93
  },
94
94
  "dependencies": {
95
95
  "@apollo/client": "^3.6.4",
96
- "@truedat/core": "4.53.6",
97
- "@truedat/df": "4.53.6",
96
+ "@truedat/core": "4.53.7",
97
+ "@truedat/df": "4.53.7",
98
98
  "graphql": "^15.5.3",
99
99
  "path-to-regexp": "^1.7.0",
100
100
  "prop-types": "^15.8.1",
@@ -114,5 +114,5 @@
114
114
  "react-dom": ">= 16.8.6 < 17",
115
115
  "semantic-ui-react": ">= 0.88.2 < 2.1"
116
116
  },
117
- "gitHead": "ff29c0e4f51c049077545c425533dd45a704e440"
117
+ "gitHead": "beccd8ba2a5af9a3e0e1690b97d7c756b59e9c2b"
118
118
  }
@@ -1,7 +1,7 @@
1
1
  import _ from "lodash/fp";
2
2
  import React, { Fragment } from "react";
3
3
  import PropTypes from "prop-types";
4
- import { Header, Icon, Segment, Table } from "semantic-ui-react";
4
+ import { Divider, Header, Icon, Segment, Table } from "semantic-ui-react";
5
5
  import { Link } from "react-router-dom";
6
6
  import { FormattedMessage } from "react-intl";
7
7
  import { useIntl } from "react-intl";
@@ -235,7 +235,7 @@ export const ConditionSummary = ({ rows, type, icon, alias }) => {
235
235
  <Header.Content>
236
236
  {areMultidimensionalRows ? (
237
237
  <FormattedMessage
238
- id={`ruleImplementations.summary.headers.${type}s`}
238
+ id={`ruleImplementations.summary.headers.${type}.plural`}
239
239
  />
240
240
  ) : (
241
241
  <FormattedMessage
@@ -245,30 +245,39 @@ export const ConditionSummary = ({ rows, type, icon, alias }) => {
245
245
  </Header.Content>
246
246
  </Header>
247
247
  {wrappedRows.map((rows, i) => (
248
- <Segment key={i}>
249
- <Table basic="very">
250
- <Table.Header>
251
- <Table.Row>
252
- <Table.HeaderCell>
253
- <FormattedMessage id={`ruleImplementation.summary.field`} />
254
- </Table.HeaderCell>
255
- <Table.HeaderCell>
256
- <FormattedMessage
257
- id={`ruleImplementation.summary.operator`}
258
- />
259
- </Table.HeaderCell>
260
- <Table.HeaderCell>
261
- <FormattedMessage id={`ruleImplementation.summary.values`} />
262
- </Table.HeaderCell>
263
- </Table.Row>
264
- </Table.Header>
265
- <Table.Body>
266
- {rows.map((row, i) => (
267
- <ConditionCell key={i} row={row} alias={alias} />
268
- ))}
269
- </Table.Body>
270
- </Table>
271
- </Segment>
248
+ <Fragment key={i}>
249
+ <Segment>
250
+ <Table basic="very">
251
+ <Table.Header>
252
+ <Table.Row>
253
+ <Table.HeaderCell>
254
+ <FormattedMessage id={`ruleImplementation.summary.field`} />
255
+ </Table.HeaderCell>
256
+ <Table.HeaderCell>
257
+ <FormattedMessage
258
+ id={`ruleImplementation.summary.operator`}
259
+ />
260
+ </Table.HeaderCell>
261
+ <Table.HeaderCell>
262
+ <FormattedMessage
263
+ id={`ruleImplementation.summary.values`}
264
+ />
265
+ </Table.HeaderCell>
266
+ </Table.Row>
267
+ </Table.Header>
268
+ <Table.Body>
269
+ {rows.map((row, i) => (
270
+ <ConditionCell key={i} row={row} alias={alias} />
271
+ ))}
272
+ </Table.Body>
273
+ </Table>
274
+ </Segment>
275
+ {i < wrappedRows.length - 1 ? (
276
+ <Divider horizontal>
277
+ <FormattedMessage id={"ruleImplementations.or"} />
278
+ </Divider>
279
+ ) : null}
280
+ </Fragment>
272
281
  ))}
273
282
  </>
274
283
  );
@@ -173,22 +173,22 @@ export const ImplementationSummary = ({ ruleImplementation, activeSteps }) => {
173
173
  alias={alias}
174
174
  />
175
175
  ) : null}
176
- {validations && _.includes("validations")(steps) && (
176
+ {validations && _.includes("validations")(steps) ? (
177
177
  <ConditionSummary
178
178
  type="validations"
179
179
  icon="setting"
180
180
  rows={validations}
181
181
  alias={alias}
182
182
  />
183
- )}
184
- {segments && _.includes("segments")(steps) && (
183
+ ) : null}
184
+ {segments && _.includes("segments")(steps) ? (
185
185
  <FieldSummary
186
186
  type="segments"
187
187
  icon="grid layout"
188
188
  rows={segments}
189
189
  alias={alias}
190
190
  />
191
- )}
191
+ ) : null}
192
192
  </>
193
193
  );
194
194
  };
@@ -98,8 +98,8 @@ const conditionAttributes = (condition) =>
98
98
  _.map(updateConditionKey)
99
99
  )(condition);
100
100
 
101
- const populationsAttributes = (populations) =>
102
- _.flow(_.map(conditionAttributes))(populations);
101
+ const conditionsAttributes = (conditions) =>
102
+ _.flow(_.map(conditionAttributes))(conditions);
103
103
 
104
104
  const fieldTypeFromStructure = (row, structures, operators, scope) => {
105
105
  const structure = _.prop("structure")(row);
@@ -287,17 +287,22 @@ export const NewRuleImplementation = ({
287
287
  )
288
288
  )
289
289
  )(ruleImplementationProps),
290
- validations: withConditionDefaultAlias(
291
- addFieldType(
292
- ruleImplementationProps,
293
- structuresFields,
294
- structuresSiblings,
295
- "validations",
296
- "validation",
297
- operators
298
- ),
299
- precalculatedDataset
300
- ),
290
+ validations: _.flow(
291
+ _.pathOr([], "validations"),
292
+ _.map((validation) =>
293
+ withConditionDefaultAlias(
294
+ addFieldType(
295
+ { validation: validation },
296
+ structuresFields,
297
+ structuresSiblings,
298
+ "validation",
299
+ "validation",
300
+ operators
301
+ ),
302
+ precalculatedDataset
303
+ )
304
+ )
305
+ )(ruleImplementationProps),
301
306
  segments: withConditionDefaultAlias(
302
307
  addFieldType(
303
308
  ruleImplementationProps,
@@ -330,7 +335,7 @@ export const NewRuleImplementation = ({
330
335
  dataset: [{}],
331
336
  population: [],
332
337
  populations: [],
333
- validations: [{}],
338
+ validations: [[{}]],
334
339
  segments: [{}],
335
340
  rawContent: {
336
341
  database: "",
@@ -393,36 +398,36 @@ export const NewRuleImplementation = ({
393
398
  _.isNil(_.path("value[0].id")(val)) ||
394
399
  belongsToDataset(_.path("value[0].id")(val));
395
400
 
396
- const validations_within_dataset = _.flow(
397
- _.prop("validations"),
398
- _.filter((val) => belongsToDataset(_.path("structure.id")(val))),
399
- _.filter((val) => valueInDataset(val))
401
+ const withinDataset = (val) =>
402
+ belongsToDataset(_.path("structure.id")(val)) && valueInDataset(val);
403
+
404
+ const populationsWithinDataset = _.flow(
405
+ _.prop("populations"),
406
+ _.map((population) => _.filter(withinDataset)(population)),
407
+ _.reject(_.isEmpty)
400
408
  )(ruleImplementation);
401
409
 
402
- const segments_within_dataset = _.flow(
403
- _.prop("segments"),
404
- _.filter((val) => belongsToDataset(_.path("structure.id")(val))),
405
- _.filter((val) => valueInDataset(val))
410
+ const validationsWithinDataset = _.flow(
411
+ _.prop("validations"),
412
+ _.map((validation) => _.filter(withinDataset)(validation)),
413
+ _.reject(_.isEmpty)
406
414
  )(ruleImplementation);
407
415
 
408
- const population_within_dataset = _.flow(
409
- _.prop("population"),
410
- _.filter((val) => belongsToDataset(_.path("structure.id")(val))),
411
- _.filter((val) => valueInDataset(val))
416
+ const segmentsWithinDataset = _.flow(
417
+ _.prop("segments"),
418
+ _.filter(withinDataset)
412
419
  )(ruleImplementation);
413
420
 
414
421
  setRuleImplementation({
415
422
  ...ruleImplementation,
416
423
  dataset: cleanDataset,
417
- population: _.isEmpty(population_within_dataset)
424
+ populations: _.isEmpty(populationsWithinDataset)
418
425
  ? []
419
- : population_within_dataset,
420
- validations: _.isEmpty(validations_within_dataset)
421
- ? [{}]
422
- : validations_within_dataset,
423
- segments: _.isEmpty(segments_within_dataset)
424
- ? []
425
- : segments_within_dataset,
426
+ : populationsWithinDataset,
427
+ validations: _.isEmpty(validationsWithinDataset)
428
+ ? [[{}]]
429
+ : validationsWithinDataset,
430
+ segments: _.isEmpty(segmentsWithinDataset) ? [] : segmentsWithinDataset,
426
431
  });
427
432
  };
428
433
 
@@ -446,11 +451,27 @@ export const NewRuleImplementation = ({
446
451
  setRuleImplementation({ ...ruleImplementation, populations: populations });
447
452
  };
448
453
 
449
- const setValidations = (validations) => {
450
- setRuleImplementation({ ...ruleImplementation, validations });
454
+ const addValidation = () => {
455
+ const validations = _.concat(
456
+ _.pathOr([[{}]], "validations")(ruleImplementation)
457
+ )([[{}]]);
458
+ setRuleImplementation({ ...ruleImplementation, validations: validations });
459
+ };
460
+
461
+ const setValidations = (validation, index) => {
462
+ const validations = _.flow(
463
+ _.pathOr([], "validations"),
464
+ _.update(`[${index}]`, (_previousValidation) => validation),
465
+ _.reject(_.isEmpty)
466
+ )(ruleImplementation);
467
+
468
+ setRuleImplementation({
469
+ ...ruleImplementation,
470
+ validations: _.isEmpty(validations) ? [[{}]] : validations,
471
+ });
451
472
  };
452
473
 
453
- const addSegments = () => {
474
+ const addSegment = () => {
454
475
  const segments = _.concat(_.pathOr([], "segments")(ruleImplementation))([
455
476
  {},
456
477
  ]);
@@ -495,8 +516,8 @@ export const NewRuleImplementation = ({
495
516
  : {
496
517
  executable: ruleImplementation.executable,
497
518
  dataset: datasetAttributes(ruleImplementation.dataset),
498
- populations: populationsAttributes(populations),
499
- validations: conditionAttributes(validations),
519
+ populations: conditionsAttributes(populations),
520
+ validation: conditionsAttributes(validations),
500
521
  segments,
501
522
  implementation_key: ruleImplementation.implementationKey,
502
523
  implementation_type: ruleImplementation.implementationType,
@@ -555,7 +576,8 @@ export const NewRuleImplementation = ({
555
576
  ) : (
556
577
  <RuleImplementationForm
557
578
  addPopulation={addPopulation}
558
- addSegments={addSegments}
579
+ addValidation={addValidation}
580
+ addSegment={addSegment}
559
581
  onSubmit={doSubmit}
560
582
  onChange={onChange}
561
583
  operators={operators}
@@ -12,7 +12,7 @@ const renderOpts = {
12
12
  "quality.threshold": "threshold",
13
13
  "quality.goal": "goal",
14
14
  "ruleImplementations.props.result_type.percentage": "percentage",
15
- "ruleImplementations.summary.headers.populations": "populations",
15
+ "ruleImplementations.summary.headers.population.plural": "populations",
16
16
  "ruleImplementation.summary.field": "Field",
17
17
  "ruleImplementation.summary.operator": "Operator",
18
18
  "ruleImplementation.summary.structure": "Structure",
@@ -149,20 +149,22 @@ describe("<NewRuleImplementation> NewRuleImplementation doSubmit", () => {
149
149
  it("doSubmit: is empty operator", async () => {
150
150
  const expectedRuleImplementation = {
151
151
  ...expectedRuleImplementationBase,
152
- validations: [
153
- {
154
- modifier: null,
155
- operator: { name: "empty" },
156
- population: [],
157
- structure: {
158
- id: 11127109,
159
- parent_index: 4,
160
- name: "EMAIL",
161
- type: "column",
152
+ validation: [
153
+ [
154
+ {
155
+ modifier: null,
156
+ operator: { name: "empty" },
157
+ population: [],
158
+ structure: {
159
+ id: 11127109,
160
+ parent_index: 4,
161
+ name: "EMAIL",
162
+ type: "column",
163
+ },
164
+ value: [],
165
+ value_modifier: [],
162
166
  },
163
- value: [],
164
- value_modifier: [],
165
- },
167
+ ],
166
168
  ],
167
169
  };
168
170
 
@@ -245,39 +247,41 @@ describe("<NewRuleImplementation> NewRuleImplementation doSubmit", () => {
245
247
  it("doSubmit: unique across fields operator", async () => {
246
248
  const expectedRuleImplementation = {
247
249
  ...expectedRuleImplementationBase,
248
- validations: [
249
- {
250
- modifier: null,
251
- operator: { name: "unique", value_type: "field_list" },
252
- population: [],
253
- structure: {
254
- id: 11127109,
255
- parent_index: 4,
256
- name: "EMAIL",
257
- type: "column",
258
- },
259
- value: [
260
- {
261
- fields: [
262
- {
263
- id: 11127109,
264
- name: "EMAIL",
265
- parent_index: 4,
266
- path: undefined,
267
- type: "column",
268
- },
269
- {
270
- id: 11127116,
271
- name: "PHONE_NUMBER",
272
- parent_index: 4,
273
- path: undefined,
274
- type: "column",
275
- },
276
- ],
250
+ validation: [
251
+ [
252
+ {
253
+ modifier: null,
254
+ operator: { name: "unique", value_type: "field_list" },
255
+ population: [],
256
+ structure: {
257
+ id: 11127109,
258
+ parent_index: 4,
259
+ name: "EMAIL",
260
+ type: "column",
277
261
  },
278
- ],
279
- value_modifier: [],
280
- },
262
+ value: [
263
+ {
264
+ fields: [
265
+ {
266
+ id: 11127109,
267
+ name: "EMAIL",
268
+ parent_index: 4,
269
+ path: undefined,
270
+ type: "column",
271
+ },
272
+ {
273
+ id: 11127116,
274
+ name: "PHONE_NUMBER",
275
+ parent_index: 4,
276
+ path: undefined,
277
+ type: "column",
278
+ },
279
+ ],
280
+ },
281
+ ],
282
+ value_modifier: [],
283
+ },
284
+ ],
281
285
  ],
282
286
  };
283
287
 
@@ -205,32 +205,34 @@ export const newRuleImplementationProps = {
205
205
  df_content: {},
206
206
  },
207
207
  validations: [
208
- {
209
- operator: {
210
- name: "length_not_eq",
211
- value_type: "number",
212
- },
213
- structure: {
214
- external_id: "/data/example.txt[agent]",
215
- id: 4814766,
216
- metadata: {
217
- metadata: { order: "6" },
208
+ [
209
+ {
210
+ operator: {
211
+ name: "length_not_eq",
212
+ value_type: "number",
218
213
  },
219
- name: "agent",
220
- path: ["Main", "example.txt"],
221
- system: {
222
- external_id: "file_system",
223
- id: 33,
224
- name: "File System",
214
+ structure: {
215
+ external_id: "/data/example.txt[agent]",
216
+ id: 4814766,
217
+ metadata: {
218
+ metadata: { order: "6" },
219
+ },
220
+ name: "agent",
221
+ path: ["Main", "example.txt"],
222
+ system: {
223
+ external_id: "file_system",
224
+ id: 33,
225
+ name: "File System",
226
+ },
227
+ type: "Column",
225
228
  },
226
- type: "Column",
229
+ value: [
230
+ {
231
+ raw: 2,
232
+ },
233
+ ],
227
234
  },
228
- value: [
229
- {
230
- raw: 2,
231
- },
232
- ],
233
- },
235
+ ],
234
236
  ],
235
237
  segments: [
236
238
  {
@@ -13,7 +13,6 @@ const structuresFields = {
13
13
  11127104: [
14
14
  {
15
15
  data_structure_id: 11127108,
16
- data_type_class: "number",
17
16
  deleted_at: null,
18
17
  description:
19
18
  "Department id where employee works; foreign key to department_id column of the departments table",
@@ -21,6 +20,7 @@ const structuresFields = {
21
20
  links: [],
22
21
  name: "DEPARTMENT_ID",
23
22
  metadata: {
23
+ data_type_class: "number",
24
24
  database: "xe",
25
25
  default: "None",
26
26
  host: "localhost",
@@ -34,13 +34,13 @@ const structuresFields = {
34
34
  },
35
35
  {
36
36
  data_structure_id: 11127111,
37
- data_type_class: "string",
38
37
  deleted_at: null,
39
38
  description: "First name of the employee. A not null column.",
40
39
  inserted_at: "2022-04-12T14:28:06.057667Z",
41
40
  links: [],
42
41
  name: "FIRST_NAME",
43
42
  metadata: {
43
+ data_type_class: "string",
44
44
  database: "xe",
45
45
  default: "None",
46
46
  host: "localhost",
@@ -54,7 +54,6 @@ const structuresFields = {
54
54
  },
55
55
  {
56
56
  data_structure_id: 11127117,
57
- data_type_class: "number",
58
57
  deleted_at: null,
59
58
  description:
60
59
  "Monthly salary of the employee. Must be greater than zero (enforced by constraint emp_salary_min)",
@@ -75,7 +74,6 @@ const structuresFields = {
75
74
  },
76
75
  {
77
76
  data_structure_id: 11127116,
78
- data_type_class: "string",
79
77
  deleted_at: null,
80
78
  description:
81
79
  "Phone number of the employee; includes country code and area code",
@@ -84,6 +82,7 @@ const structuresFields = {
84
82
  name: "PHONE_NUMBER",
85
83
  type: "column",
86
84
  metadata: {
85
+ data_type_class: "string",
87
86
  database: "xe",
88
87
  default: "None",
89
88
  host: "localhost",
@@ -97,7 +96,6 @@ const structuresFields = {
97
96
  },
98
97
  {
99
98
  data_structure_id: 11127115,
100
- data_type_class: "number",
101
99
  deleted_at: null,
102
100
  description:
103
101
  "Manager id of the employee; has same domain as manager_id in departments table. Foreign key to employee_id column of employees table. (useful for reflexive joins and CONNECT BY query)",
@@ -106,6 +104,7 @@ const structuresFields = {
106
104
  name: "MANAGER_ID",
107
105
  type: "NUMBER",
108
106
  metadata: {
107
+ data_type_class: "number",
109
108
  database: "xe",
110
109
  default: "None",
111
110
  host: "localhost",
@@ -118,7 +117,6 @@ const structuresFields = {
118
117
  },
119
118
  {
120
119
  data_structure_id: 11127109,
121
- data_type_class: "string",
122
120
  deleted_at: null,
123
121
  description: "Email id of the employee",
124
122
  inserted_at: "2022-04-12T14:28:06.057667Z",
@@ -126,6 +124,7 @@ const structuresFields = {
126
124
  name: "EMAIL",
127
125
  type: "column",
128
126
  metadata: {
127
+ data_type_class: "string",
129
128
  database: "xe",
130
129
  default: "None",
131
130
  host: "localhost",
@@ -139,13 +138,13 @@ const structuresFields = {
139
138
  },
140
139
  {
141
140
  data_structure_id: 11127110,
142
- data_type_class: "number",
143
141
  deleted_at: null,
144
142
  description: "Primary key of employees table.",
145
143
  inserted_at: "2022-04-12T14:28:06.057667Z",
146
144
  links: [],
147
145
  name: "EMPLOYEE_ID",
148
146
  metadata: {
147
+ data_type_class: "number",
149
148
  database: "xe",
150
149
  default: "None",
151
150
  host: "localhost",
@@ -160,13 +159,13 @@ const structuresFields = {
160
159
  },
161
160
  {
162
161
  data_structure_id: 11127114,
163
- data_type_class: "string",
164
162
  deleted_at: null,
165
163
  description: "Last name of the employee. A not null column.",
166
164
  inserted_at: "2022-04-12T14:28:06.057667Z",
167
165
  links: [],
168
166
  name: "LAST_NAME",
169
167
  metadata: {
168
+ data_type_class: "string",
170
169
  database: "xe",
171
170
  default: "None",
172
171
  host: "localhost",
@@ -180,7 +179,6 @@ const structuresFields = {
180
179
  },
181
180
  {
182
181
  data_structure_id: 11127112,
183
- data_type_class: "date",
184
182
  deleted_at: null,
185
183
  description:
186
184
  "Date when the employee started on this job. A not null column.",
@@ -188,6 +186,7 @@ const structuresFields = {
188
186
  links: [],
189
187
  name: "HIRE_DATE",
190
188
  metadata: {
189
+ data_type_class: "date",
191
190
  database: "xe",
192
191
  default: "None",
193
192
  host: "localhost",
@@ -201,7 +200,6 @@ const structuresFields = {
201
200
  },
202
201
  {
203
202
  data_structure_id: 11127113,
204
- data_type_class: "string",
205
203
  deleted_at: null,
206
204
  description:
207
205
  "Current job of the employee; foreign key to job_id column of the jobs table. A not null column.",
@@ -209,6 +207,7 @@ const structuresFields = {
209
207
  links: [],
210
208
  name: "JOB_ID",
211
209
  metadata: {
210
+ data_type_class: "string",
212
211
  database: "xe",
213
212
  default: "None",
214
213
  host: "localhost",
@@ -222,7 +221,6 @@ const structuresFields = {
222
221
  },
223
222
  {
224
223
  data_structure_id: 11127107,
225
- data_type_class: "number",
226
224
  deleted_at: null,
227
225
  description:
228
226
  "Commission percentage of the employee; Only employees in sales department elgible for commission percentage",
@@ -230,6 +228,7 @@ const structuresFields = {
230
228
  links: [],
231
229
  name: "COMMISSION_PCT",
232
230
  metadata: {
231
+ data_type_class: "number",
233
232
  database: "xe",
234
233
  default: "None",
235
234
  host: "localhost",
@@ -374,7 +373,7 @@ const ruleImplementation = {
374
373
  rule_id: null,
375
374
  segments: [],
376
375
  status: "draft",
377
- validations: [],
376
+ validations: [[]],
378
377
  version: 2,
379
378
  };
380
379
 
@@ -653,7 +653,7 @@ exports[`<NewRuleImplementation /> calculate aliases when not informed 1`] = `
653
653
  <div
654
654
  class="content"
655
655
  >
656
- Validation
656
+ Validations
657
657
  </div>
658
658
  </h3>
659
659
  <div
@@ -1370,6 +1370,67 @@ exports[`<NewRuleImplementation /> matches the latest snapshot 1`] = `
1370
1370
  </div>
1371
1371
  </div>
1372
1372
  </div>
1373
+ <h3
1374
+ class="ui header"
1375
+ >
1376
+ <i
1377
+ aria-hidden="true"
1378
+ class="setting small icon"
1379
+ />
1380
+ <div
1381
+ class="content"
1382
+ >
1383
+ Validations
1384
+ </div>
1385
+ </h3>
1386
+ <div
1387
+ class="ui segment"
1388
+ >
1389
+ <table
1390
+ class="ui very basic table"
1391
+ >
1392
+ <thead
1393
+ class=""
1394
+ >
1395
+ <tr
1396
+ class=""
1397
+ >
1398
+ <th
1399
+ class=""
1400
+ >
1401
+ Field
1402
+ </th>
1403
+ <th
1404
+ class=""
1405
+ >
1406
+ Operator
1407
+ </th>
1408
+ <th
1409
+ class=""
1410
+ >
1411
+ Values
1412
+ </th>
1413
+ </tr>
1414
+ </thead>
1415
+ <tbody
1416
+ class=""
1417
+ >
1418
+ <tr
1419
+ class=""
1420
+ >
1421
+ <td
1422
+ class="five wide"
1423
+ />
1424
+ <td
1425
+ class="five wide"
1426
+ />
1427
+ <td
1428
+ class="five wide"
1429
+ />
1430
+ </tr>
1431
+ </tbody>
1432
+ </table>
1433
+ </div>
1373
1434
  </div>
1374
1435
  </div>
1375
1436
  </div>
@@ -142,7 +142,7 @@ export const FiltersGrid = ({
142
142
 
143
143
  return (
144
144
  <>
145
- <Grid verticalAlign="middle" divided="vertically">
145
+ <Grid verticalAlign="middle">
146
146
  <FiltersGroup
147
147
  activeConditionIndex={activeConditionIndex}
148
148
  allOperators={allOperators}
@@ -1,7 +1,7 @@
1
1
  import _ from "lodash/fp";
2
- import React, { useState } from "react";
2
+ import React, { Fragment, useState } from "react";
3
3
  import { useIntl } from "react-intl";
4
- import { Accordion, Grid, Icon, Popup } from "semantic-ui-react";
4
+ import { Accordion, Divider, Grid, Icon, Popup } from "semantic-ui-react";
5
5
  import PropTypes from "prop-types";
6
6
  import ValueConditions from "./ValueConditions";
7
7
  import FiltersFormGroup from "./FiltersFormGroup";
@@ -158,24 +158,26 @@ export const FiltersGroup = ({
158
158
  scope,
159
159
  }) => {
160
160
  return rows.map((clause, index) => (
161
- <Filter
162
- key={index}
163
- activeConditionIndex={activeConditionIndex}
164
- allOperators={allOperators}
165
- operators={operators}
166
- composeValue={composeValue}
167
- clause={clause}
168
- index={index}
169
- onConditionChange={onConditionChange}
170
- onOperatorChange={onOperatorChange}
171
- onStructureChange={onStructureChange}
172
- onValueChange={onValueChange}
173
- onModifierChange={onModifierChange}
174
- parentStructures={parentStructures}
175
- setActiveConditionIndex={setActiveConditionIndex}
176
- setRowValue={setRowValue}
177
- scope={scope}
178
- />
161
+ <Fragment key={index}>
162
+ <Filter
163
+ activeConditionIndex={activeConditionIndex}
164
+ allOperators={allOperators}
165
+ operators={operators}
166
+ composeValue={composeValue}
167
+ clause={clause}
168
+ index={index}
169
+ onConditionChange={onConditionChange}
170
+ onOperatorChange={onOperatorChange}
171
+ onStructureChange={onStructureChange}
172
+ onValueChange={onValueChange}
173
+ onModifierChange={onModifierChange}
174
+ parentStructures={parentStructures}
175
+ setActiveConditionIndex={setActiveConditionIndex}
176
+ setRowValue={setRowValue}
177
+ scope={scope}
178
+ />
179
+ {index < rows.length - 1 ? <Divider /> : null}
180
+ </Fragment>
179
181
  ));
180
182
  };
181
183
 
@@ -1,5 +1,5 @@
1
1
  import _ from "lodash/fp";
2
- import React, { useEffect, useState } from "react";
2
+ import React, { Fragment, useEffect, useState } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
5
  import { useHistory } from "react-router-dom";
@@ -45,7 +45,8 @@ RuleImplementationStep.propTypes = {
45
45
 
46
46
  export const RuleImplementationForm = ({
47
47
  addPopulation,
48
- addSegments,
48
+ addSegment,
49
+ addValidation,
49
50
  isSubmitting,
50
51
  onChange,
51
52
  onSubmit,
@@ -122,10 +123,6 @@ export const RuleImplementationForm = ({
122
123
  !!ruleImplementation?.domain_id &&
123
124
  areLimitsValid(ruleImplementation);
124
125
 
125
- const validValidations = () =>
126
- _.every(_.negate(_.isEmpty))(validations) &&
127
- validationsComplete(validations);
128
-
129
126
  const validSegments = () => _.every(_.negate(_.isEmpty))(segments);
130
127
 
131
128
  const validDataSet = () => areStructuresComplete();
@@ -158,7 +155,7 @@ export const RuleImplementationForm = ({
158
155
  {
159
156
  name: "validations",
160
157
  icon: "setting",
161
- isValid: () => validValidations(),
158
+ isValid: () => validValidations(validations),
162
159
  },
163
160
  ...(canManageSegmentsAnyDomain
164
161
  ? [
@@ -192,7 +189,13 @@ export const RuleImplementationForm = ({
192
189
  _.every(_.negate(_.isEmpty))(population) &&
193
190
  _.every((p) => itemComplete(p, attrsFromProfile(p)))(population);
194
191
 
192
+ const validValidations = (validationsSet) =>
193
+ _.every(
194
+ (validations) => _.negate(_.isEmpty) && validationsComplete(validations)
195
+ )(validationsSet);
196
+
195
197
  const validationsComplete = (validations) =>
198
+ _.negate(_.isEmpty)(validations) &&
196
199
  _.every((v) => itemComplete(v, attrsFromProfile(v)))(validations);
197
200
 
198
201
  const attrsFromProfile = (p) => {
@@ -361,14 +364,23 @@ export const RuleImplementationForm = ({
361
364
  {activeStep == "populations" ? (
362
365
  <>
363
366
  {populations.map((population, i) => (
364
- <Segment key={i}>
365
- <PopulationForm
366
- population={population}
367
- setPopulation={(p) => setPopulations(p, i)}
368
- structures={structures}
369
- typeOperators={operators}
370
- />
371
- </Segment>
367
+ <Fragment key={i}>
368
+ <Segment>
369
+ <PopulationForm
370
+ population={population}
371
+ setPopulation={(p) => setPopulations(p, i)}
372
+ structures={structures}
373
+ typeOperators={operators}
374
+ />
375
+ </Segment>
376
+ {i < populations.length - 1 ? (
377
+ <Divider horizontal>
378
+ {formatMessage({
379
+ id: "ruleImplementations.or",
380
+ })}
381
+ </Divider>
382
+ ) : null}
383
+ </Fragment>
372
384
  ))}
373
385
  {_.flow(_.flatten, _.isEmpty)(populations) ? (
374
386
  <PopulationTypeForm createFilter={() => addPopulation()} />
@@ -383,18 +395,42 @@ export const RuleImplementationForm = ({
383
395
  ) : null}
384
396
  </>
385
397
  ) : null}
386
- {activeStep == "validations" && (
387
- <ValidationsForm
388
- validations={validations}
389
- setValidations={setValidations}
390
- structures={structures}
391
- typeOperators={operators}
392
- />
393
- )}
398
+ {activeStep == "validations" ? (
399
+ <>
400
+ {validations.map((validation, i) => (
401
+ <Fragment key={i}>
402
+ <Segment>
403
+ <ValidationsForm
404
+ validations={validation}
405
+ setValidations={(v) => setValidations(v, i)}
406
+ structures={structures}
407
+ typeOperators={operators}
408
+ />
409
+ </Segment>
410
+ {i < validations.length - 1 ? (
411
+ <Divider horizontal>
412
+ {formatMessage({
413
+ id: "ruleImplementations.or",
414
+ })}
415
+ </Divider>
416
+ ) : null}
417
+ </Fragment>
418
+ ))}
419
+ {validValidations(validations) ? (
420
+ <Button
421
+ secondary
422
+ onClick={() => addValidation()}
423
+ content={formatMessage({
424
+ id: "ruleImplementationForm.validations.add",
425
+ })}
426
+ />
427
+ ) : null}
428
+ </>
429
+ ) : null}
394
430
  {activeStep == "segments" ? (
395
431
  <>
396
432
  {_.flow(_.flatten, _.isEmpty)(segments) ? (
397
- <SegmentsTypeForm createFilter={() => addSegments()} />
433
+ <SegmentsTypeForm createFilter={() => addSegment()} />
398
434
  ) : (
399
435
  <SegmentsForm
400
436
  segments={segments}
@@ -466,7 +502,8 @@ export const RuleImplementationForm = ({
466
502
 
467
503
  RuleImplementationForm.propTypes = {
468
504
  addPopulation: PropTypes.func,
469
- addSegments: PropTypes.func,
505
+ addValidation: PropTypes.func,
506
+ addSegment: PropTypes.func,
470
507
  authManageSegments: PropTypes.bool,
471
508
  isAdmin: PropTypes.bool,
472
509
  isSubmitting: PropTypes.bool,
@@ -17,8 +17,7 @@ export const ValidationsForm = ({
17
17
  const validation = { ..._.nth(index)(validations), ...value };
18
18
  setValidations(replaceAt(index, validation)(validations));
19
19
  } else {
20
- const array = dropAt(index)(validations);
21
- setValidations(_.isEmpty(array) ? [{}] : array);
20
+ setValidations(dropAt(index)(validations));
22
21
  }
23
22
  };
24
23
 
@@ -467,12 +467,14 @@ export default {
467
467
  "ruleImplementation.system.placeholder": "Select a system",
468
468
  "ruleImplementation.table": "Table",
469
469
  "ruleImplementation.table.placeholder": "Select a table",
470
+ "ruleImplementationForm.condition.add": "Add condition",
470
471
  "ruleImplementationForm.populations.add": "Add population",
471
472
  "ruleImplementationForm.step.dataset": "Data Set",
472
473
  "ruleImplementationForm.step.information": "Information",
473
474
  "ruleImplementationForm.step.populations": "Populations",
474
475
  "ruleImplementationForm.step.segments": "Segments",
475
476
  "ruleImplementationForm.step.validations": "Validations",
477
+ "ruleImplementationForm.validations.add": "Add validations",
476
478
  "ruleImplementationRawForm.props.database": "Database",
477
479
  "ruleImplementationRawForm.props.database.placeholder": "Select database",
478
480
  "ruleImplementationRawForm.props.dataset": "Dataset",
@@ -512,6 +514,7 @@ export default {
512
514
  "ruleImplementations.events.action_restored": "Implementation restored",
513
515
  "ruleImplementations.events.action_updated": "Implementation updated",
514
516
  "ruleImplementations.events.action_versioned": "Implementation versioned",
517
+ "ruleImplementations.or": "OR",
515
518
  "ruleImplementations.props.business_concept": "Concept",
516
519
  "ruleImplementations.props.executable": "Executable",
517
520
  "ruleImplementations.props.executable.false": "Internal",
@@ -549,9 +552,10 @@ export default {
549
552
  "ruleImplementations.summary.headers.filters": "Filters",
550
553
  "ruleImplementations.summary.headers.implementation": "Rule Implementation",
551
554
  "ruleImplementations.summary.headers.population": "Population",
552
- "ruleImplementations.summary.headers.populations": "Populations",
555
+ "ruleImplementations.summary.headers.population.plural": "Populations",
553
556
  "ruleImplementations.summary.headers.segments": "Segments",
554
- "ruleImplementations.summary.headers.validations": "Validation",
557
+ "ruleImplementations.summary.headers.validations": "Validations",
558
+ "ruleImplementations.summary.headers.validations.plural": "Validations",
555
559
  "ruleImplementations.update.success.header.implementation_unchanged":
556
560
  "The implementation has not changed",
557
561
  "ruleImplementations.upload.failed.header":
@@ -477,12 +477,14 @@ export default {
477
477
  "ruleImplementation.system.placeholder": "Seleccionar sistema",
478
478
  "ruleImplementation.table": "Tabla",
479
479
  "ruleImplementation.table.placeholder": "Seleccionar tabla",
480
+ "ruleImplementationForm.condition.add": "Añadir condición",
480
481
  "ruleImplementationForm.populations.add": "Añadir población",
481
482
  "ruleImplementationForm.step.dataset": "Datos",
482
483
  "ruleImplementationForm.step.information": "Información",
483
484
  "ruleImplementationForm.step.populations": "Poblaciones",
484
485
  "ruleImplementationForm.step.segments": "Segmentos",
485
486
  "ruleImplementationForm.step.validations": "Validaciones",
487
+ "ruleImplementationForm.validations.add": "Añadir validación",
486
488
  "ruleImplementationRawForm.props.database": "Base de datos",
487
489
  "ruleImplementationRawForm.props.database.placeholder":
488
490
  "Seleccionar base de datos",
@@ -528,6 +530,7 @@ export default {
528
530
  "ruleImplementations.events.action_restored": "Implementación restaurada",
529
531
  "ruleImplementations.events.action_updated": "Implementación actualizada",
530
532
  "ruleImplementations.events.action_versioned": "Implementación versionada",
533
+ "ruleImplementations.or": "OR",
531
534
  "ruleImplementations.props.business_concept": "Concepto",
532
535
  "ruleImplementations.props.executable": "Ejecutable",
533
536
  "ruleImplementations.props.executable.false": "Interna",
@@ -570,9 +573,10 @@ export default {
570
573
  "ruleImplementations.summary.headers.implementation":
571
574
  "Implementación de una Regla",
572
575
  "ruleImplementations.summary.headers.population": "Población",
573
- "ruleImplementations.summary.headers.populations": "Poblaciones",
576
+ "ruleImplementations.summary.headers.population.plural": "Poblaciones",
574
577
  "ruleImplementations.summary.headers.segments": "Segmentos",
575
578
  "ruleImplementations.summary.headers.validations": "Validaciones",
579
+ "ruleImplementations.summary.headers.validations.plural": "Validaciones",
576
580
  "ruleImplementations.update.success.header.implementation_unchanged":
577
581
  "La implementación no ha cambiado",
578
582
  "ruleImplementations.upload.failed.header":
@@ -39,7 +39,10 @@ const ruleImplementation = (state = initialState, { type, payload }) => {
39
39
  case fetchRuleImplementation.TRIGGER:
40
40
  return initialState;
41
41
  case fetchRuleImplementation.SUCCESS:
42
- return pickFields(payload.data);
42
+ return pickFields({
43
+ ...payload.data,
44
+ validations: payload.data.validation,
45
+ });
43
46
  case clearRuleImplementation.TRIGGER:
44
47
  return initialState;
45
48
  default: