@genesislcap/pbc-reporting-ui 14.396.3 → 14.397.0

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.
Files changed (73) hide show
  1. package/dist/dts/new/main/edit-config/col-filters/col-filters-grid.helpers.d.ts.map +1 -1
  2. package/dist/dts/new/main/edit-config/datasource-config/datasource-config-item.d.ts +6 -2
  3. package/dist/dts/new/main/edit-config/datasource-config/datasource-config-item.d.ts.map +1 -1
  4. package/dist/dts/new/main/edit-config/datasource-config/datasource-config-item.styles.d.ts.map +1 -1
  5. package/dist/dts/new/main/edit-config/datasource-config/datasource-config-item.template.d.ts.map +1 -1
  6. package/dist/dts/new/main/edit-config/datasource-config/datasources-config-container.helpers.d.ts.map +1 -1
  7. package/dist/dts/new/main/edit-config/datasource-config/datasources-config-container.template.d.ts.map +1 -1
  8. package/dist/dts/new/main/edit-config/shared/datasource-data-base-component.helpers.ts.d.ts.map +1 -1
  9. package/dist/dts/new/store/slices/datasources-config.d.ts +20 -6
  10. package/dist/dts/new/store/slices/datasources-config.d.ts.map +1 -1
  11. package/dist/dts/new/store/slices/types.d.ts +19 -2
  12. package/dist/dts/new/store/slices/types.d.ts.map +1 -1
  13. package/dist/dts/new/store/store.d.ts +133 -35
  14. package/dist/dts/new/store/store.d.ts.map +1 -1
  15. package/dist/dts/new/types/misc.d.ts +4 -2
  16. package/dist/dts/new/types/misc.d.ts.map +1 -1
  17. package/dist/dts/new/utils/alias-generator.d.ts +8 -0
  18. package/dist/dts/new/utils/alias-generator.d.ts.map +1 -0
  19. package/dist/dts/new/utils/alias-generator.test.d.ts +2 -0
  20. package/dist/dts/new/utils/alias-generator.test.d.ts.map +1 -0
  21. package/dist/dts/new/utils/index.d.ts +1 -0
  22. package/dist/dts/new/utils/index.d.ts.map +1 -1
  23. package/dist/dts/new/utils/tooltip.d.ts +3 -0
  24. package/dist/dts/new/utils/tooltip.d.ts.map +1 -1
  25. package/dist/dts/new/utils/transformers.d.ts +1 -1
  26. package/dist/dts/new/utils/transformers.d.ts.map +1 -1
  27. package/dist/esm/new/main/edit-config/col-filters/col-filters-grid.helpers.js +3 -1
  28. package/dist/esm/new/main/edit-config/col-filters/col-filters-grid.helpers.test.js +71 -6
  29. package/dist/esm/new/main/edit-config/col-rename-alias/col-rename-alias-grid.helpers.test.js +32 -4
  30. package/dist/esm/new/main/edit-config/data-transforms-derived-fields/data-transforms.helpers.test.js +10 -2
  31. package/dist/esm/new/main/edit-config/datasource-config/datasource-config-item.js +100 -10
  32. package/dist/esm/new/main/edit-config/datasource-config/datasource-config-item.styles.js +6 -0
  33. package/dist/esm/new/main/edit-config/datasource-config/datasource-config-item.template.js +38 -3
  34. package/dist/esm/new/main/edit-config/datasource-config/datasources-config-container.helpers.js +11 -7
  35. package/dist/esm/new/main/edit-config/datasource-config/datasources-config-container.helpers.test.js +29 -22
  36. package/dist/esm/new/main/edit-config/datasource-config/datasources-config-container.template.js +3 -0
  37. package/dist/esm/new/main/edit-config/shared/datasource-data-base-component.helpers.ts.js +16 -8
  38. package/dist/esm/new/main/edit-config/shared/datasource-data-base-component.test.js +13 -2
  39. package/dist/esm/new/main/edit-config/tabbed-datasource-container/tabbed-datasource-container.template.js +1 -1
  40. package/dist/esm/new/store/slices/datasources-config.js +50 -11
  41. package/dist/esm/new/store/slices/types.js +1 -0
  42. package/dist/esm/new/utils/alias-generator.js +16 -0
  43. package/dist/esm/new/utils/alias-generator.test.js +36 -0
  44. package/dist/esm/new/utils/index.js +1 -0
  45. package/dist/esm/new/utils/tooltip.js +16 -0
  46. package/dist/esm/new/utils/transformers.js +20 -6
  47. package/dist/esm/new/utils/transformers.test.js +61 -11
  48. package/dist/esm/new/utils/validators.test.js +35 -21
  49. package/dist/tsconfig.tsbuildinfo +1 -1
  50. package/package.json +22 -22
  51. package/src/new/main/edit-config/col-filters/col-filters-grid.helpers.test.ts +76 -6
  52. package/src/new/main/edit-config/col-filters/col-filters-grid.helpers.ts +4 -1
  53. package/src/new/main/edit-config/col-rename-alias/col-rename-alias-grid.helpers.test.ts +32 -4
  54. package/src/new/main/edit-config/data-transforms-derived-fields/data-transforms.helpers.test.ts +10 -2
  55. package/src/new/main/edit-config/datasource-config/datasource-config-item.styles.ts +6 -0
  56. package/src/new/main/edit-config/datasource-config/datasource-config-item.template.ts +62 -3
  57. package/src/new/main/edit-config/datasource-config/datasource-config-item.ts +107 -8
  58. package/src/new/main/edit-config/datasource-config/datasources-config-container.helpers.test.ts +32 -23
  59. package/src/new/main/edit-config/datasource-config/datasources-config-container.helpers.ts +18 -10
  60. package/src/new/main/edit-config/datasource-config/datasources-config-container.template.ts +6 -0
  61. package/src/new/main/edit-config/shared/datasource-data-base-component.helpers.ts.ts +21 -11
  62. package/src/new/main/edit-config/shared/datasource-data-base-component.test.ts +14 -2
  63. package/src/new/main/edit-config/tabbed-datasource-container/tabbed-datasource-container.template.ts +1 -1
  64. package/src/new/store/slices/datasources-config.ts +71 -16
  65. package/src/new/store/slices/types.ts +22 -4
  66. package/src/new/types/misc.ts +9 -2
  67. package/src/new/utils/alias-generator.test.ts +44 -0
  68. package/src/new/utils/alias-generator.ts +18 -0
  69. package/src/new/utils/index.ts +1 -0
  70. package/src/new/utils/tooltip.ts +19 -0
  71. package/src/new/utils/transformers.test.ts +73 -11
  72. package/src/new/utils/transformers.ts +30 -6
  73. package/src/new/utils/validators.test.ts +35 -21
@@ -28,6 +28,8 @@ LookupColumnRename('should return original data when no rename is found', () =>
28
28
  INPUT_TYPE: 'REQ_REP' as const,
29
29
  OUTPUT_TYPE: 'TABLE' as const,
30
30
  TRANSFORMER_CONFIGURATION: { COLUMN_RENAMES: {} },
31
+ GROUPING_STRATEGY: 'NONE',
32
+ GROUP_KEY: null,
31
33
  });
32
34
 
33
35
  const result = lookupColumnRename(mockData);
@@ -56,6 +58,8 @@ LookupColumnRename('should return data with renamed display name when rename exi
56
58
  [originalColumn]: newDisplayName,
57
59
  },
58
60
  },
61
+ GROUPING_STRATEGY: 'NONE',
62
+ GROUP_KEY: null,
59
63
  });
60
64
 
61
65
  const result = lookupColumnRename(mockData);
@@ -81,6 +85,8 @@ LookupColumnRename('should handle undefined COLUMN_RENAMES', () => {
81
85
  INPUT_TYPE: 'REQ_REP' as const,
82
86
  OUTPUT_TYPE: 'TABLE' as const,
83
87
  TRANSFORMER_CONFIGURATION: {},
88
+ GROUPING_STRATEGY: 'NONE',
89
+ GROUP_KEY: null,
84
90
  });
85
91
 
86
92
  const result = lookupColumnRename(mockData);
@@ -101,13 +107,24 @@ DisplayNameOnChangeHandler('calls setColumnRename with correct parameters when v
101
107
  const callback = sinon.spy();
102
108
  const handler = displayNameOnChangeHandler(callback);
103
109
  const mockData = {
104
- [GridColumnNames.Datasource]: 'testSource (Snapshot)',
110
+ [GridColumnNames.Datasource]: 'testAlias (Snapshot)',
105
111
  [GridColumnNames.ColumnName]: 'originalColumn',
106
112
  [GridColumnNames.DisplayName]: 'Original Display',
107
113
  [GridColumnNames.Type]: 'STRING',
108
114
  } as ColumnRenameAliasRowData;
109
115
 
110
116
  const setColumnRenameStub = sinon.stub(actions.datasourceConfig, 'setColumnRename');
117
+ sinon.stub(selectors.datasourceConfig, 'getAllConfigSet').returns({
118
+ REQ_REP_testSource_testAlias: {
119
+ KEY: 'testAlias',
120
+ NAME: 'testSource',
121
+ INPUT_TYPE: 'REQ_REP',
122
+ OUTPUT_TYPE: 'TABLE',
123
+ TRANSFORMER_CONFIGURATION: {},
124
+ GROUPING_STRATEGY: 'NONE',
125
+ GROUP_KEY: null,
126
+ },
127
+ });
111
128
 
112
129
  handler('newColumn', { data: mockData } as ICellRendererParams<ColumnRenameAliasRowData>);
113
130
 
@@ -115,7 +132,7 @@ DisplayNameOnChangeHandler('calls setColumnRename with correct parameters when v
115
132
 
116
133
  // Check each property individually instead of using deepEqual
117
134
  const firstCallArg = setColumnRenameStub.firstCall.args[0];
118
- assert.equal(firstCallArg.key, 'REQ_REP_testSource');
135
+ assert.equal(firstCallArg.key, 'REQ_REP_testSource_testAlias');
119
136
  assert.equal(firstCallArg.columnFrom, 'originalColumn');
120
137
  assert.equal(firstCallArg.columnTo, 'newColumn');
121
138
 
@@ -126,13 +143,24 @@ DisplayNameOnChangeHandler('removes prohibited characters and calls callback', (
126
143
  const callback = sinon.spy();
127
144
  const handler = displayNameOnChangeHandler(callback);
128
145
  const mockData = {
129
- [GridColumnNames.Datasource]: 'testSource (Snapshot)',
146
+ [GridColumnNames.Datasource]: 'testAlias (Snapshot)',
130
147
  [GridColumnNames.ColumnName]: 'originalColumn',
131
148
  [GridColumnNames.DisplayName]: 'Original Display',
132
149
  [GridColumnNames.Type]: 'STRING',
133
150
  } as ColumnRenameAliasRowData;
134
151
 
135
152
  const setColumnRenameStub = sinon.stub(actions.datasourceConfig, 'setColumnRename');
153
+ sinon.stub(selectors.datasourceConfig, 'getAllConfigSet').returns({
154
+ REQ_REP_testSource_testAlias: {
155
+ KEY: 'testAlias',
156
+ NAME: 'testSource',
157
+ INPUT_TYPE: 'REQ_REP',
158
+ OUTPUT_TYPE: 'TABLE',
159
+ TRANSFORMER_CONFIGURATION: {},
160
+ GROUPING_STRATEGY: 'NONE',
161
+ GROUP_KEY: null,
162
+ },
163
+ });
136
164
 
137
165
  handler('new@Column-=+test', { data: mockData } as ICellRendererParams<ColumnRenameAliasRowData>);
138
166
 
@@ -140,7 +168,7 @@ DisplayNameOnChangeHandler('removes prohibited characters and calls callback', (
140
168
 
141
169
  // Check each property individually instead of using deepEqual
142
170
  const firstCallArg = setColumnRenameStub.firstCall.args[0];
143
- assert.equal(firstCallArg.key, 'REQ_REP_testSource');
171
+ assert.equal(firstCallArg.key, 'REQ_REP_testSource_testAlias');
144
172
  assert.equal(firstCallArg.columnFrom, 'originalColumn');
145
173
  assert.equal(firstCallArg.columnTo, 'newColumntest');
146
174
 
@@ -49,6 +49,8 @@ LookupColumnFormat('should return empty string when no format is found', () => {
49
49
  INPUT_TYPE: 'REQ_REP' as const,
50
50
  OUTPUT_TYPE: 'TABLE' as const,
51
51
  TRANSFORMER_CONFIGURATION: { COLUMN_FORMATS: {} },
52
+ GROUPING_STRATEGY: 'NONE',
53
+ GROUP_KEY: null,
52
54
  });
53
55
 
54
56
  const result = lookupColumnFormat(mockData);
@@ -77,6 +79,8 @@ LookupColumnFormat('should return data with format when format exists', () => {
77
79
  [originalColumn]: newFormat,
78
80
  },
79
81
  },
82
+ GROUPING_STRATEGY: 'NONE',
83
+ GROUP_KEY: null,
80
84
  });
81
85
 
82
86
  const result = lookupColumnFormat(mockData);
@@ -102,6 +106,8 @@ LookupColumnFormat('should handle undefined COLUMN_FORMATS', () => {
102
106
  INPUT_TYPE: 'REQ_REP' as const,
103
107
  OUTPUT_TYPE: 'TABLE' as const,
104
108
  TRANSFORMER_CONFIGURATION: {},
109
+ GROUPING_STRATEGY: 'NONE',
110
+ GROUP_KEY: null,
105
111
  });
106
112
 
107
113
  const result = lookupColumnFormat(mockData);
@@ -128,6 +134,8 @@ LookupColumnFormat('should return existing format when format exists', () => {
128
134
  numericColumn: '#.##',
129
135
  },
130
136
  },
137
+ GROUPING_STRATEGY: 'NONE',
138
+ GROUP_KEY: null,
131
139
  });
132
140
 
133
141
  const result = lookupColumnFormat(mockData);
@@ -468,7 +476,7 @@ ValueExprCellRendereronChange.before.each(() => {
468
476
  });
469
477
 
470
478
  ValueExprCellRendereronChange('should call setColumnTransform when model exists', () => {
471
- const datasourceKey = 'REQ_REP_Test';
479
+ const datasourceKey = 'REQ_REP_RESOURCE_ALIAS';
472
480
  const columnName = 'testColumn';
473
481
 
474
482
  // Using type assertions to match the expected type
@@ -518,7 +526,7 @@ ValueExprCellRendereronChange('should call setColumnTransform when model exists'
518
526
  });
519
527
 
520
528
  ValueExprCellRendereronChange('should call deleteColumnTransform when model is null', () => {
521
- const datasourceKey = 'REQ_REP_Test';
529
+ const datasourceKey = 'REQ_REP_RESOURCE_ALIAS';
522
530
  const columnName = 'testColumn';
523
531
 
524
532
  const mockParams = {
@@ -9,6 +9,12 @@ export const styles = css`
9
9
  gap: calc(var(--design-unit) * 2px);
10
10
  }
11
11
 
12
+ div.split {
13
+ display: grid;
14
+ grid-template-columns: 1fr 1fr;
15
+ gap: calc(var(--design-unit) * 2px);
16
+ }
17
+
12
18
  rapid-combobox.datasource {
13
19
  margin-bottom: calc(var(--design-unit) * 2px);
14
20
  }
@@ -1,6 +1,12 @@
1
1
  import { Combobox } from '@genesislcap/foundation-ui';
2
2
  import { classNames, html, repeat, when, whenElse } from '@genesislcap/web-core';
3
- import { datasourceOutputs, DatasourceOutputTypes, selectors } from '../../../store';
3
+ import {
4
+ datasourceGroupingStrategies,
5
+ DatasourceGroupingStrategy,
6
+ datasourceOutputs,
7
+ DatasourceOutputTypes,
8
+ selectors,
9
+ } from '../../../store';
4
10
  import { datasourceInputForDisplay, TOOLTIP, tooltip } from '../../../utils';
5
11
  import type { DatasourceConfigItem } from './datasource-config-item';
6
12
  import { DatasourceChoice } from './types';
@@ -10,11 +16,52 @@ const datasourceAlias = html<DatasourceConfigItem>`
10
16
  <p class="label">Name${tooltip(TOOLTIP.DATASOURCES_NAME)}</p>
11
17
  <rapid-text-field
12
18
  value=${(x) => selectors.datasourceConfig.getDatasource(x.datasourceName!).KEY}
13
- @change=${(x, ctx) => x.updateKeyHandler((<HTMLInputElement>ctx.event.target).value)}
19
+ @change=${(x, ctx) =>
20
+ x.updateKeyHandler((<HTMLInputElement>ctx.event.target).value, ctx.event)}
14
21
  ></rapid-text-field>
15
22
  </div>
16
23
  `;
17
24
 
25
+ const groupingType = html<DatasourceConfigItem>`
26
+ <div class="split">
27
+ <div>
28
+ <p class="label">Grouping Strategy${tooltip(TOOLTIP.DATASOURCES_GROUPING_STRATEGY)}</p>
29
+ <rapid-select
30
+ :value=${(x) =>
31
+ selectors.datasourceConfig.getDatasource(x.datasourceName!).GROUPING_STRATEGY}
32
+ @change=${(x, ctx) =>
33
+ x.updateGroupingStrategyHandler(
34
+ (<HTMLSelectElement>ctx.event.target).value as DatasourceGroupingStrategy,
35
+ )}
36
+ >
37
+ ${repeat(
38
+ () => datasourceGroupingStrategies,
39
+ html<string>`
40
+ <rapid-option>${(x) => x}</rapid-option>
41
+ `,
42
+ )}
43
+ </rapid-select>
44
+ </div>
45
+ <div>
46
+ <p class="label">Grouping Key${tooltip(TOOLTIP.DATASOURCES_GROUPING_KEY)}</p>
47
+ <rapid-select
48
+ :value=${(x) => selectors.datasourceConfig.getDatasource(x.datasourceName!).GROUP_KEY}
49
+ @change=${(x, ctx) =>
50
+ x.updateGroupingByHandler((<HTMLSelectElement>ctx.event.target).value)}
51
+ ?disabled=${(x) =>
52
+ selectors.datasourceConfig.getDatasource(x.datasourceName!).GROUPING_STRATEGY === 'NONE'}
53
+ >
54
+ ${repeat(
55
+ (x) => x.groupingByFieldsNames ?? [],
56
+ html<string>`
57
+ <rapid-option>${(x) => x}</rapid-option>
58
+ `,
59
+ )}
60
+ </rapid-select>
61
+ </div>
62
+ </div>
63
+ `;
64
+
18
65
  const processType = html<DatasourceConfigItem>`
19
66
  <div>
20
67
  <p class="label">Type${tooltip(TOOLTIP.DATASOURCES_TYPE)}</p>
@@ -46,6 +93,9 @@ const closeButton = html<DatasourceConfigItem>`
46
93
  const component = html<DatasourceConfigItem>`
47
94
  ${when((x) => x.canRemove, closeButton)}
48
95
  <div class="container">
96
+ <div class="${() => classNames(['split', Boolean(selectors.baseConfig.getConfig().DOCUMENT_TEMPLATE_ID)])}">
97
+ <div>
98
+ <p class="label">Datasource${tooltip(TOOLTIP.DATASOURCES_DATASOURCE)}</p>
49
99
  <rapid-combobox
50
100
  class="datasource ${(x) => classNames(['error', x.error !== 'none'])}"
51
101
  autocomplete="both"
@@ -66,7 +116,16 @@ const component = html<DatasourceConfigItem>`
66
116
  `,
67
117
  )}
68
118
  </rapid-combobox>
69
- ${when((_) => Boolean(selectors.baseConfig.getConfig().DOCUMENT_TEMPLATE_ID), datasourceAlias)}
119
+ </div>
120
+ ${when(
121
+ (_) =>
122
+ Boolean(selectors.baseConfig.getConfig().DOCUMENT_TEMPLATE_ID) ||
123
+ selectors.baseConfig.getConfig().OUTPUT_FORMAT !== 'CSV',
124
+ datasourceAlias,
125
+ )}
126
+ </div>
127
+ </div>
128
+ ${when((_) => Boolean(selectors.baseConfig.getConfig().DOCUMENT_TEMPLATE_ID), groupingType)}
70
129
  <!-- ${when((_) => selectors.baseConfig.getConfig().OUTPUT_FORMAT === 'CSV', processType)} -->
71
130
  </div>
72
131
  `;
@@ -3,14 +3,17 @@ import { Combobox } from '@genesislcap/foundation-ui';
3
3
  import { attr, customElement, GenesisElement, observable } from '@genesislcap/web-core';
4
4
  import {
5
5
  actions,
6
+ DatasourceGroupingStrategy,
6
7
  DatasourceInputTypes,
7
8
  DatasourceName,
8
9
  DatasourceOutputTypes,
10
+ selectors,
9
11
  } from '../../../store';
10
12
  import { Display } from '../../../types';
11
13
  import {
12
14
  buildDatasourceName,
13
15
  datasourceInputFromDisplay,
16
+ generateUniqueAlias,
14
17
  getDatasourceSchema,
15
18
  showNotificationToast,
16
19
  } from '../../../utils';
@@ -30,11 +33,23 @@ export class DatasourceConfigItem extends GenesisElement {
30
33
 
31
34
  @observable datasourceName: DatasourceName | null = null;
32
35
  @observable datasourceChoices: DatasourceChoice[] | null = null;
36
+ @observable groupingByFieldsNames: string[] | null = null;
33
37
  @attr({ mode: 'boolean', attribute: 'can-remove' }) canRemove: boolean = false;
34
38
 
35
39
  @attr position: 'below' | 'above' = 'below';
36
40
  @attr error: 'none' | 'invalid-datasource' | 'unknown' = 'none';
37
41
 
42
+ async connectedCallback(): Promise<void> {
43
+ super.connectedCallback();
44
+ try {
45
+ const datasource = selectors.datasourceConfig.getDatasource(this.datasourceName);
46
+ const schema = await this.getSchema(datasource.NAME);
47
+ this.groupingByFieldsNames = Object.keys(schema.properties);
48
+ } catch (e) {
49
+ console.error(e);
50
+ }
51
+ }
52
+
38
53
  deleteDatasourceHandler() {
39
54
  if (!this.canRemove) throw new Error('Trying to remove final datasource!');
40
55
  if (!this.datasourceName) throw new Error('deleteDatasourceHandler - Datasource key unset!');
@@ -43,13 +58,31 @@ export class DatasourceConfigItem extends GenesisElement {
43
58
  });
44
59
  }
45
60
 
46
- updateKeyHandler(newKey: string) {
61
+ updateKeyHandler(newName: string, event?: Event) {
47
62
  if (!this.datasourceName) {
48
63
  throw new Error('updateKeyHandler - datasourceName unset');
49
64
  }
50
- actions.datasourceConfig.updateDatasourceKey({
65
+
66
+ const allDatasources = selectors.datasourceConfig.getAllConfigSet();
67
+ const nameExists = Object.entries(allDatasources)
68
+ .filter(([key]) => key !== this.datasourceName)
69
+ .some(([, ds]) => ds.KEY.toLowerCase() === newName.toLowerCase());
70
+
71
+ if (nameExists) {
72
+ showNotificationToast({
73
+ title: 'Reporting Config',
74
+ body: `Name '${newName}' is already in use by another datasource.`,
75
+ toast: { type: 'error' as any },
76
+ });
77
+ if (event) {
78
+ (event.target as HTMLInputElement).value = allDatasources[this.datasourceName].KEY;
79
+ }
80
+ return;
81
+ }
82
+
83
+ actions.datasourceConfig.renameDatasource({
51
84
  key: this.datasourceName,
52
- newKey,
85
+ newName,
53
86
  });
54
87
  }
55
88
 
@@ -63,6 +96,45 @@ export class DatasourceConfigItem extends GenesisElement {
63
96
  });
64
97
  }
65
98
 
99
+ async updateGroupingStrategyHandler(value: DatasourceGroupingStrategy) {
100
+ try {
101
+ if (value === 'NONE') {
102
+ actions.datasourceConfig.deleteGrouping({ key: this.datasourceName });
103
+ return;
104
+ }
105
+
106
+ const dsResource = selectors.datasourceConfig.getDatasource(this.datasourceName).NAME;
107
+ const schema = await this.getSchema(dsResource);
108
+ if (!schema.properties) throw new Error(`schema properties for ${dsResource} undefined`);
109
+
110
+ const fields = Object.keys(schema.properties);
111
+ actions.datasourceConfig.setGrouping({
112
+ key: this.datasourceName,
113
+ grouping: { GROUPING_STRATEGY: value, GROUP_KEY: fields[0] },
114
+ });
115
+ this.groupingByFieldsNames = Object.keys(schema.properties);
116
+ } catch (e) {
117
+ const errorMsg = e instanceof Error ? `\n\n${e.message}` : '';
118
+ showNotificationToast({
119
+ title: 'Reporting Config',
120
+ body: `Error fetching field data for datasource ${this.datasourceName}.${errorMsg}`,
121
+ toast: { type: 'critical' as any },
122
+ });
123
+ this.error = 'unknown';
124
+ console.error(e);
125
+ }
126
+ }
127
+
128
+ updateGroupingByHandler(value: string) {
129
+ const datasource = selectors.datasourceConfig.getDatasource(this.datasourceName);
130
+ if (datasource.GROUPING_STRATEGY === 'NONE')
131
+ throw new Error('Cannot set group by field when GROUPING_STRATEGY is NONE');
132
+ actions.datasourceConfig.setGrouping({
133
+ key: this.datasourceName,
134
+ grouping: { GROUPING_STRATEGY: datasource.GROUPING_STRATEGY, GROUP_KEY: value },
135
+ });
136
+ }
137
+
66
138
  async updateDatasourceSourceHandler(selectedOption: Combobox) {
67
139
  try {
68
140
  if ((selectedOption?.selectedOptions ?? []).filter((x) => x).length === 0) {
@@ -72,7 +144,7 @@ export class DatasourceConfigItem extends GenesisElement {
72
144
  return;
73
145
  }
74
146
  const option = selectedOption.selectedOptions[0];
75
- const KEY = option.value;
147
+ const RESOURCE_NAME = option.value;
76
148
  const dataAttr = <Display.DatasourceInputTypes | null>(
77
149
  option.getAttribute('data-bubble-content')
78
150
  );
@@ -83,22 +155,49 @@ export class DatasourceConfigItem extends GenesisElement {
83
155
  throw new Error('updateKeyHandler - datasourceName or INPUT_TYPE null');
84
156
  }
85
157
 
86
- const schema = await this.getSchema(KEY);
87
- if (!schema.properties) throw new Error(`schema properties for ${KEY} undefined`);
158
+ const schema = await this.getSchema(RESOURCE_NAME);
159
+ if (!schema.properties) throw new Error(`schema properties for ${RESOURCE_NAME} undefined`);
88
160
 
89
161
  const fields = Object.keys(schema.properties);
90
162
 
163
+ // Check for duplicate aliases
164
+ const allDatasources = selectors.datasourceConfig.getAllConfigSet();
165
+ const existingAliases = new Set(
166
+ Object.entries(allDatasources)
167
+ .filter(([key]) => key !== this.datasourceName)
168
+ .map(([, ds]) => ds.KEY),
169
+ );
170
+
171
+ const uniqueAlias = generateUniqueAlias(RESOURCE_NAME, existingAliases);
172
+
173
+ // Preserve index
174
+ const originalIndex = Object.keys(allDatasources).indexOf(this.datasourceName);
175
+
91
176
  actions.datasourceConfig.deleteDatasource({ key: this.datasourceName });
92
177
  actions.datasourceConfig.initDatasourceConfiguration({
93
178
  base: {
94
- KEY,
179
+ KEY: uniqueAlias,
180
+ NAME: RESOURCE_NAME,
95
181
  INPUT_TYPE: INPUT_TYPE as DatasourceInputTypes,
96
182
  },
97
183
  fields,
98
184
  });
99
- const datasourceName = buildDatasourceName(KEY, INPUT_TYPE as DatasourceInputTypes);
185
+ const datasourceName = buildDatasourceName(
186
+ uniqueAlias,
187
+ RESOURCE_NAME,
188
+ INPUT_TYPE as DatasourceInputTypes,
189
+ );
190
+
191
+ if (originalIndex !== -1) {
192
+ actions.datasourceConfig.reorderDatasource({
193
+ key: datasourceName,
194
+ newIndex: originalIndex,
195
+ });
196
+ }
197
+
100
198
  setDefaultFormatsForDatasource(datasourceName, schema);
101
199
  this.error = 'none';
200
+ this.groupingByFieldsNames = Object.keys(schema.properties);
102
201
  } catch (e: unknown) {
103
202
  const errorMsg = e instanceof Error ? `\n\n${e.message}` : '';
104
203
  showNotificationToast({
@@ -190,15 +190,17 @@ CreateNewDatasourceConfig.before.each(() => {
190
190
  });
191
191
 
192
192
  CreateNewDatasourceConfig('should exit when no valid datasources are available', async () => {
193
- const mockChoices = [{ name: 'test', inputType: 'REQ_REP' as DatasourceInputTypes }];
193
+ const mockChoices = [{ name: 'testSource', inputType: 'REQ_REP' as DatasourceInputTypes }];
194
194
 
195
195
  const getAllConfigSetStub = sinon.stub(selectors.datasourceConfig, 'getAllConfigSet').returns({
196
- REQ_REP_test: {
197
- KEY: 'REQ_REP_test',
198
- NAME: 'Test Datasource',
196
+ REQ_REP_testSource_testSource: {
197
+ KEY: 'testSource',
198
+ NAME: 'testSource',
199
199
  INPUT_TYPE: 'REQ_REP' as const,
200
200
  OUTPUT_TYPE: 'TABLE' as const,
201
201
  TRANSFORMER_CONFIGURATION: { INCLUDE_COLUMNS: [] },
202
+ GROUPING_STRATEGY: 'NONE',
203
+ GROUP_KEY: null,
202
204
  },
203
205
  });
204
206
 
@@ -215,15 +217,17 @@ CreateNewDatasourceConfig('should exit when no valid datasources are available',
215
217
  CreateNewDatasourceConfig(
216
218
  'should exit when trying to add multiple datasources with CSV format',
217
219
  async () => {
218
- const mockChoices = [{ name: 'test', inputType: 'REQ_REP' as DatasourceInputTypes }];
220
+ const mockChoices = [{ name: 'testSource', inputType: 'REQ_REP' as DatasourceInputTypes }];
219
221
 
220
222
  const getAllConfigSetStub = sinon.stub(selectors.datasourceConfig, 'getAllConfigSet').returns({
221
- REQ_REP_existing: {
222
- KEY: 'REQ_REP_existing',
223
- NAME: 'Existing Datasource',
223
+ REQ_REP_existingSource_existingAlias: {
224
+ KEY: 'existingAlias',
225
+ NAME: 'existingSource',
224
226
  INPUT_TYPE: 'REQ_REP' as const,
225
227
  OUTPUT_TYPE: 'TABLE' as const,
226
228
  TRANSFORMER_CONFIGURATION: { INCLUDE_COLUMNS: [] },
229
+ GROUPING_STRATEGY: 'NONE' as const,
230
+ GROUP_KEY: null,
227
231
  },
228
232
  });
229
233
 
@@ -238,7 +242,7 @@ CreateNewDatasourceConfig(
238
242
 
239
243
  await createNewDatasourceConfig(getSchemaStub)(mockChoices);
240
244
 
241
- assert.equal(getAllConfigSetStub.callCount, 2, 'getAllConfigSetStub should be called twice');
245
+ assert.equal(getAllConfigSetStub.callCount, 1, 'getAllConfigSetStub should be called once');
242
246
  assert.equal(getConfigStub.callCount, 1, 'getConfigStub should be called once');
243
247
  // assert.ok(loggerStub.calledWith('Cannot add multiple datasources when using csv file without a template'));
244
248
  if (loggerStub.args.length > 0) {
@@ -256,15 +260,17 @@ CreateNewDatasourceConfig(
256
260
  CreateNewDatasourceConfig(
257
261
  'should allow adding multiple datasources with CSV format IF template is selected',
258
262
  async () => {
259
- const mockChoices = [{ name: 'test', inputType: 'REQ_REP' as DatasourceInputTypes }];
263
+ const mockChoices = [{ name: 'testSource', inputType: 'REQ_REP' as DatasourceInputTypes }];
260
264
 
261
265
  const getAllConfigSetStub = sinon.stub(selectors.datasourceConfig, 'getAllConfigSet').returns({
262
- REQ_REP_existing: {
263
- KEY: 'REQ_REP_existing',
264
- NAME: 'Existing Datasource',
266
+ REQ_REP_existingSource_existingAlias: {
267
+ KEY: 'existingAlias',
268
+ NAME: 'existingSource',
265
269
  INPUT_TYPE: 'REQ_REP' as const,
266
270
  OUTPUT_TYPE: 'TABLE' as const,
267
271
  TRANSFORMER_CONFIGURATION: { INCLUDE_COLUMNS: [] },
272
+ GROUPING_STRATEGY: 'NONE' as const,
273
+ GROUP_KEY: null,
268
274
  },
269
275
  });
270
276
 
@@ -287,14 +293,14 @@ CreateNewDatasourceConfig(
287
293
 
288
294
  await createNewDatasourceConfig(getSchemaStub)(mockChoices);
289
295
 
290
- assert.ok(getAllConfigSetStub.calledTwice);
296
+ assert.ok(getAllConfigSetStub.calledOnce);
291
297
  assert.ok(getSchemaStub.calledOnce);
292
298
  assert.ok(initConfigStub.called);
293
299
  },
294
300
  );
295
301
 
296
302
  CreateNewDatasourceConfig('should successfully create new datasource configuration', async () => {
297
- const mockChoices = [{ name: 'test', inputType: 'REQ_REP' as DatasourceInputTypes }];
303
+ const mockChoices = [{ name: 'testSource', inputType: 'REQ_REP' as DatasourceInputTypes }];
298
304
 
299
305
  const getAllConfigSetStub = sinon.stub(selectors.datasourceConfig, 'getAllConfigSet').returns({});
300
306
 
@@ -316,16 +322,19 @@ CreateNewDatasourceConfig('should successfully create new datasource configurati
316
322
 
317
323
  await createNewDatasourceConfig(getSchemaStub)(mockChoices);
318
324
 
319
- assert.ok(getAllConfigSetStub.calledTwice);
325
+ assert.ok(getAllConfigSetStub.calledOnce);
320
326
  assert.ok(getSchemaStub.calledOnce);
321
327
  assert.ok(
322
- initConfigStub.calledWith({
323
- base: {
324
- KEY: 'test',
325
- INPUT_TYPE: 'REQ_REP',
326
- },
327
- fields: ['field1', 'field2'],
328
- }),
328
+ initConfigStub.calledWith(
329
+ sinon.match({
330
+ base: sinon.match({
331
+ KEY: 'testSource',
332
+ NAME: 'testSource',
333
+ INPUT_TYPE: 'REQ_REP',
334
+ }),
335
+ fields: sinon.match.array.deepEquals(['field1', 'field2']),
336
+ }),
337
+ ),
329
338
  );
330
339
  });
331
340
 
@@ -1,7 +1,7 @@
1
1
  import { Connect, logger } from '@genesislcap/foundation-comms';
2
2
  import { actions, selectors } from '../../../store';
3
3
  import { Genesis } from '../../../types';
4
- import { buildDatasourceName } from '../../../utils';
4
+ import { buildDatasourceName, generateUniqueAlias } from '../../../utils';
5
5
  import { setDefaultFormatsForDatasource } from '../data-transforms-derived-fields/data-transforms.helpers';
6
6
  import { DatasourceChoice } from './types';
7
7
 
@@ -10,21 +10,19 @@ export type GetGenesisSchema = (resource: string) => Promise<Genesis.JSONSchema7
10
10
  export const createNewDatasourceConfig =
11
11
  (getSchema: GetGenesisSchema) =>
12
12
  async (choices: DatasourceChoice[]): Promise<void> => {
13
+ const allDatasources = selectors.datasourceConfig.getAllConfigSet();
13
14
  const validDatasources =
14
15
  choices.filter(
15
- (ds) =>
16
- !(
17
- buildDatasourceName(ds.name, ds.inputType) in
18
- selectors.datasourceConfig.getAllConfigSet()
19
- ),
16
+ (ds) => !(buildDatasourceName(ds.name, ds.name, ds.inputType) in allDatasources),
20
17
  ) ?? [];
18
+
21
19
  if (validDatasources?.length < 1) {
22
20
  logger.error('No datasources available to create new config from');
23
21
  return;
24
22
  }
25
23
  const baseConfig = selectors.baseConfig.getConfig();
26
24
  if (
27
- Object.keys(selectors.datasourceConfig.getAllConfigSet()).length > 0 &&
25
+ Object.keys(allDatasources).length > 0 &&
28
26
  baseConfig.OUTPUT_FORMAT === 'CSV' &&
29
27
  !baseConfig.DOCUMENT_TEMPLATE_ID
30
28
  ) {
@@ -32,13 +30,23 @@ export const createNewDatasourceConfig =
32
30
  return;
33
31
  }
34
32
 
35
- const { KEY, INPUT_TYPE, schema } = await findFirstValidDatasource(getSchema)(validDatasources);
33
+ const {
34
+ KEY: RESOURCE_NAME,
35
+ INPUT_TYPE,
36
+ schema,
37
+ } = await findFirstValidDatasource(getSchema)(validDatasources);
38
+
39
+ // Check for duplicate aliases
40
+ const existingAliases = new Set(Object.values(allDatasources).map((ds) => ds.KEY));
41
+
42
+ const uniqueAlias = generateUniqueAlias(RESOURCE_NAME, existingAliases);
36
43
 
37
- const datasourceName = buildDatasourceName(KEY, INPUT_TYPE);
44
+ const datasourceName = buildDatasourceName(uniqueAlias, RESOURCE_NAME, INPUT_TYPE);
38
45
 
39
46
  actions.datasourceConfig.initDatasourceConfiguration({
40
47
  base: {
41
- KEY,
48
+ KEY: uniqueAlias,
49
+ NAME: RESOURCE_NAME,
42
50
  INPUT_TYPE,
43
51
  },
44
52
  fields: Object.keys(schema.properties!), // Properties is valid as we check in findFirstValidDatasource
@@ -37,6 +37,12 @@ const container = html<DatasourcesConfigContainer>`
37
37
  ${repeat(
38
38
  (_) => Object.keys(selectors.datasourceConfig.getAllConfigSet()),
39
39
  html<string, DatasourcesConfigContainer>`
40
+ ${when(
41
+ (_, ctx) => !ctx.isFirst,
42
+ html`
43
+ <rapid-divider></rapid-divider>
44
+ `,
45
+ )}
40
46
  <datasource-config-item
41
47
  :datasourceName=${(x) => x}
42
48
  :datasourceChoices=${(_, c) => c.parent.datasourceChoices}