Skip to content

Commit 5daa359

Browse files
ChronosSFmpavlinov
authored andcommitted
[groupby] page both group rows and records - master (#5266)
* chore(*): testing allpages grouping tests * fix(grid): changing pipes around so that summaries work #5089 * chore(*): some test changes based on new behavior * chore(*): highlight updates * chore(*): further solving issues with search * chore(*): addressing regressions * chore(*): applying correct code for atinexistingpage * chore(*): solving incorrect metadata inflation * chore(*): Move parameters related to Group By in from Base to GridComponent since it is not relevant to other grids.
1 parent 8f4af7b commit 5daa359

18 files changed

+289
-303
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ All notable changes for each version of this project will be documented in this
1212
- You can use this mode to apply directives on the tab items - for example to achieve routing navigation.
1313
- You are allowed to customize tab items with labels, icons and even templates.
1414
- `IgxGrid`
15+
- **Behavioral Change** - paging now includes the group rows in the page size. You may find more information about the change in the [GroupBy Specification](https://github.com/IgniteUI/igniteui-angular/wiki/Group-By-Specification)
1516
- `IgxColumnGroup`
1617
- Re-templating the column group header is now possible using the `headerTemplate` input property or the `igxHeader` directive.
1718
- `igx-grid-footer`

projects/igniteui-angular/src/lib/data-operations/data-util.spec.ts

Lines changed: 89 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { DefaultSortingStrategy } from './sorting-strategy';
77
import { cloneArray } from '../core/utils';
88
import { ISortingExpression, SortingDirection } from './sorting-expression.interface';
99
import { DataUtil } from './data-util';
10-
import { IGroupByResult } from './grouping-strategy';
10+
import { IGroupByResult } from './grouping-result.interface';
1111
import { IGroupingState } from './groupby-state.interface';
1212
import { IGroupByRecord } from './groupby-record.interface';
1313
import { FilteringStrategy } from './filtering-strategy';
@@ -23,6 +23,7 @@ import {
2323
import { IPagingState, PagingError } from './paging-state.interface';
2424
import { SampleTestData } from '../test-utils/sample-test-data.spec';
2525
import { Transaction, TransactionType, HierarchicalTransaction } from '../services';
26+
import { group } from '@angular/animations';
2627

2728
/* Test sorting */
2829
function testSort() {
@@ -115,27 +116,26 @@ function testGroupBy() {
115116
describe('Test groupBy', () => {
116117
it('groups by descending column "boolean", expanded', () => {
117118
// sort
118-
let res = DataUtil.sort(data, [expr]);
119-
// first group pipe
120-
const gres = DataUtil.group(res, state);
121-
// second group pipe
122-
res = DataUtil.restoreGroups(gres, state);
123-
expect(dataGenerator.getValuesForColumn(res, 'boolean'))
119+
let result = DataUtil.sort(data, [expr]);
120+
// group by
121+
const groupResult = DataUtil.group(result, state);
122+
result = groupResult.data;
123+
expect(dataGenerator.getValuesForColumn(result, 'boolean'))
124124
.toEqual([undefined, false, false, false, undefined, true, true]);
125-
const groups: Array<IGroupByRecord> = dataGenerator.getGroupRecords(res);
126-
const group1: IGroupByRecord = gres.metadata[0];
127-
const group2: IGroupByRecord = gres.metadata[3];
125+
const groups: Array<IGroupByRecord> = dataGenerator.getGroupRecords(result);
126+
const group1: IGroupByRecord = groupResult.metadata[1];
127+
const group2: IGroupByRecord = groupResult.metadata[5];
128128
expect(groups[0]).toEqual(null);
129-
expect(res[0]).toEqual(group1);
129+
expect(result[0]).toEqual(group1);
130130
expect(groups[4]).toEqual(null);
131-
expect(res[4]).toEqual(group2);
132-
expect(gres.metadata[0]).toEqual(gres.metadata[1]);
133-
expect(gres.metadata[1]).toEqual(gres.metadata[2]);
134-
expect(gres.metadata[3]).toEqual(gres.metadata[4]);
131+
expect(result[4]).toEqual(group2);
132+
expect(groupResult.metadata[1]).toEqual(groupResult.metadata[2]);
133+
expect(groupResult.metadata[2]).toEqual(groupResult.metadata[3]);
134+
expect(groupResult.metadata[5]).toEqual(groupResult.metadata[6]);
135135
expect(group1.level).toEqual(0);
136136
expect(group2.level).toEqual(0);
137-
expect(group1.records).toEqual(res.slice(1, 4));
138-
expect(group2.records).toEqual(res.slice(5, 7));
137+
expect(group1.records).toEqual(result.slice(1, 4));
138+
expect(group2.records).toEqual(result.slice(5, 7));
139139
expect(group1.value).toEqual(false);
140140
expect(group2.value).toEqual(true);
141141
});
@@ -144,21 +144,20 @@ function testGroupBy() {
144144
state.defaultExpanded = false;
145145
// sort
146146
const sorted = DataUtil.sort(data, [expr]);
147-
// first group pipe
148-
const gres = DataUtil.group(sorted, state);
149-
// second group pipe
150-
const res = DataUtil.restoreGroups(gres, state);
151-
expect(dataGenerator.getValuesForColumn(res, 'boolean'))
147+
// group by
148+
const groupResult = DataUtil.group(sorted, state);
149+
const result = groupResult.data;
150+
expect(dataGenerator.getValuesForColumn(result, 'boolean'))
152151
.toEqual([undefined, undefined]);
153-
const groups: Array<IGroupByRecord> = dataGenerator.getGroupRecords(res);
152+
const groups: Array<IGroupByRecord> = dataGenerator.getGroupRecords(result);
154153
expect(groups[0]).toEqual(null);
155154
expect(groups[1]).toEqual(null);
156-
expect(res[0].level).toEqual(0);
157-
expect(res[1].level).toEqual(0);
158-
expect(res[0].records).toEqual(sorted.slice(0, 3));
159-
expect(res[1].records).toEqual(sorted.slice(3, 5));
160-
expect(res[0].value).toEqual(false);
161-
expect(res[1].value).toEqual(true);
155+
expect(result[0].level).toEqual(0);
156+
expect(result[1].level).toEqual(0);
157+
expect(result[0].records).toEqual(sorted.slice(0, 3));
158+
expect(result[1].records).toEqual(sorted.slice(3, 5));
159+
expect(result[0].value).toEqual(false);
160+
expect(result[1].value).toEqual(true);
162161
});
163162

164163
it('groups by ascending column "boolean", partially collapsed', () => {
@@ -168,21 +167,20 @@ function testGroupBy() {
168167
});
169168
// sort
170169
const sorted = DataUtil.sort(data, [expr]);
171-
// first group pipe
172-
const gres = DataUtil.group(sorted, state);
173-
// second group pipe
174-
const res = DataUtil.restoreGroups(gres, state);
175-
expect(dataGenerator.getValuesForColumn(res, 'boolean'))
170+
// group by
171+
const groupRecords = DataUtil.group(sorted, state);
172+
const result = groupRecords.data;
173+
expect(dataGenerator.getValuesForColumn(result, 'boolean'))
176174
.toEqual([undefined, undefined, true, true]);
177-
const groups: Array<IGroupByRecord> = dataGenerator.getGroupRecords(res);
175+
const groups: Array<IGroupByRecord> = dataGenerator.getGroupRecords(result);
178176
expect(groups[0]).toEqual(null);
179-
expect(res[1]).toEqual(gres.metadata[4]);
180-
expect(res[0].level).toEqual(0);
181-
expect(res[1].level).toEqual(0);
182-
expect(res[0].records).toEqual(sorted.slice(0, 3));
183-
expect(res[1].records).toEqual(sorted.slice(3, 5));
184-
expect(res[0].value).toEqual(false);
185-
expect(res[1].value).toEqual(true);
177+
expect(result[1]).toEqual(groupRecords.metadata[2]);
178+
expect(result[0].level).toEqual(0);
179+
expect(result[1].level).toEqual(0);
180+
expect(result[0].records).toEqual(sorted.slice(0, 3));
181+
expect(result[1].records).toEqual(sorted.slice(3, 5));
182+
expect(result[0].value).toEqual(false);
183+
expect(result[1].value).toEqual(true);
186184
});
187185

188186
it('two level groups', () => {
@@ -195,24 +193,23 @@ function testGroupBy() {
195193
state.expressions.push(expr2);
196194
// sort
197195
const sorted = DataUtil.sort(data, [expr, expr2]);
198-
// first group pipe
199-
const gres = DataUtil.group(sorted, state);
200-
// second group pipe
201-
const res = DataUtil.restoreGroups(gres, state);
202-
expect(dataGenerator.getValuesForColumn(res, 'boolean'))
196+
// group by
197+
const groupRecords = DataUtil.group(sorted, state);
198+
const result = groupRecords.data;
199+
expect(dataGenerator.getValuesForColumn(result, 'boolean'))
203200
.toEqual([undefined, undefined, false, undefined, false,
204201
undefined, false, undefined, undefined, true, undefined, true]);
205-
expect(dataGenerator.getValuesForColumn(res, 'string'))
202+
expect(dataGenerator.getValuesForColumn(result, 'string'))
206203
.toEqual([undefined, undefined, 'row0, col1', undefined, 'row2, col1',
207204
undefined, 'row4, col1', undefined, undefined, 'row1, col1', undefined, 'row3, col1']);
208-
const group1: IGroupByRecord = gres.metadata[0];
205+
const group1: IGroupByRecord = groupRecords.metadata[2];
209206
const group2: IGroupByRecord = group1.groupParent;
210-
const group3: IGroupByRecord = gres.metadata[3];
207+
const group3: IGroupByRecord = groupRecords.metadata[9];
211208
const group4: IGroupByRecord = group3.groupParent;
212-
expect(group1).toEqual(res[1]);
213-
expect(group2).toEqual(res[0]);
214-
expect(group3).toEqual(res[8]);
215-
expect(group4).toEqual(res[7]);
209+
expect(group1).toEqual(result[1]);
210+
expect(group2).toEqual(result[0]);
211+
expect(group3).toEqual(result[8]);
212+
expect(group4).toEqual(result[7]);
216213
expect(group1.level).toEqual(1);
217214
expect(group2.level).toEqual(0);
218215
expect(group3.level).toEqual(1);
@@ -222,60 +219,54 @@ function testGroupBy() {
222219
it('groups by descending column "boolean", paging', () => {
223220
// sort
224221
const sorted = DataUtil.sort(data, [expr]);
225-
// first group pipe
226-
const grouped = DataUtil.group(sorted, state);
222+
// group by
223+
const groupResult = DataUtil.group(sorted, state);
227224
// page
228225
let paged: IGroupByResult = {
229-
data: cloneArray(grouped.data),
230-
metadata: cloneArray(grouped.metadata)
226+
data: cloneArray(groupResult.data),
227+
metadata: cloneArray(groupResult.metadata)
231228
};
232-
paged.data = DataUtil.page(paged.data, { index: 0, recordsPerPage: 2 });
233-
paged.metadata = DataUtil.page(paged.metadata, { index: 0, recordsPerPage: 2 });
234-
// second group pipe
235-
let res = DataUtil.restoreGroups(paged, state);
236-
expect(dataGenerator.getValuesForColumn(res, 'boolean'))
229+
paged.data = DataUtil.page(paged.data, { index: 0, recordsPerPage: 3 });
230+
paged.metadata = DataUtil.page(paged.metadata, { index: 0, recordsPerPage: 3 });
231+
expect(dataGenerator.getValuesForColumn(paged.data, 'boolean'))
237232
.toEqual([undefined, false, false]);
238-
let groups: Array<IGroupByRecord> = dataGenerator.getGroupRecords(res);
239-
const group1: IGroupByRecord = grouped.metadata[0];
233+
let groups: Array<IGroupByRecord> = dataGenerator.getGroupRecords(paged.data);
234+
const group1: IGroupByRecord = paged.metadata[1];
240235
expect(groups[0]).toEqual(null);
241-
expect(res[0]).toEqual(group1);
242-
expect(grouped.metadata[1]).toEqual(group1);
236+
expect(paged.data[0]).toEqual(group1);
237+
expect(paged.metadata[2]).toEqual(group1);
243238
expect(group1.level).toEqual(0);
244-
expect(group1.records).toEqual(grouped.data.slice(0, 3));
239+
expect(group1.records).toEqual(sorted.slice(0, 3));
245240
expect(group1.value).toEqual(false);
246241

247242
// page 2
248243
paged = {
249-
data: cloneArray(grouped.data),
250-
metadata: cloneArray(grouped.metadata)
244+
data: cloneArray(groupResult.data),
245+
metadata: cloneArray(groupResult.metadata)
251246
};
252-
paged.data = DataUtil.page(paged.data, { index: 1, recordsPerPage: 2 });
253-
paged.metadata = DataUtil.page(paged.metadata, { index: 1, recordsPerPage: 2 });
254-
// second group pipe
255-
res = DataUtil.restoreGroups(paged, state);
256-
expect(dataGenerator.getValuesForColumn(res, 'boolean'))
257-
.toEqual([undefined, false, undefined, true]);
258-
groups = dataGenerator.getGroupRecords(res);
259-
const group2: IGroupByRecord = grouped.metadata[2];
260-
const group3: IGroupByRecord = grouped.metadata[3];
261-
expect(res[0]).toEqual(group2);
262-
expect(groups[0]).toEqual(null);
263-
expect(res[2]).toEqual(group3);
264-
expect(groups[2]).toEqual(null);
247+
paged.data = DataUtil.page(paged.data, { index: 1, recordsPerPage: 3 });
248+
paged.metadata = DataUtil.page(paged.metadata, { index: 1, recordsPerPage: 3 });
249+
expect(dataGenerator.getValuesForColumn(paged.data, 'boolean'))
250+
.toEqual([false, undefined, true]);
251+
groups = dataGenerator.getGroupRecords(paged.data);
252+
const group2: IGroupByRecord = paged.metadata[0];
253+
const group3: IGroupByRecord = paged.metadata[2];
254+
// group is split
255+
expect(group2).toEqual(group1);
256+
expect(paged.data[1]).toEqual(group3);
257+
expect(groups[1]).toEqual(null);
265258
expect(group2.value).toEqual(false);
266259
expect(group3.value).toEqual(true);
267-
expect(group2.records).toEqual(grouped.data.slice(0, 3));
268-
expect(group3.records).toEqual(grouped.data.slice(3, 5));
260+
expect(group2.records).toEqual(sorted.slice(0, 3));
261+
expect(group3.records).toEqual(sorted.slice(3, 5));
269262
});
270263

271264
it('provides groupsRecords array', () => {
272265
const groupRecords = [];
273266
// sort
274-
let res = DataUtil.sort(data, [expr]);
275-
// first group pipe
276-
let gres = DataUtil.group(res, state, null, groupRecords);
277-
// second group pipe
278-
res = DataUtil.restoreGroups(gres, state);
267+
const res = DataUtil.sort(data, [expr]);
268+
// group by
269+
DataUtil.group(res, state, null, groupRecords);
279270
expect(groupRecords.length).toEqual(2);
280271
expect(groupRecords[0].records.length).toEqual(3);
281272
expect(groupRecords[1].records.length).toEqual(2);
@@ -290,10 +281,8 @@ function testGroupBy() {
290281
state.expressions.push(expr2);
291282
// sort
292283
const sorted = DataUtil.sort(data, [expr, expr2]);
293-
// first group pipe
294-
gres = DataUtil.group(sorted, state, null, groupRecords);
295-
// second group pipe
296-
res = DataUtil.restoreGroups(gres, state);
284+
// group by
285+
DataUtil.group(sorted, state, null, groupRecords);
297286
expect(groupRecords.length).toEqual(2);
298287
expect(groupRecords[0].records.length).toEqual(3);
299288
expect(groupRecords[1].records.length).toEqual(2);
@@ -323,13 +312,12 @@ function testGroupBy() {
323312
state.defaultExpanded = false;
324313
// sort
325314
const sorted = DataUtil.sort(data, [expr, expr2, expr3]);
326-
// first group pipe
327-
const gres = DataUtil.group(sorted, state);
328-
// second group pipe
329-
const res = DataUtil.restoreGroups(gres, state);
330-
expect(res.length).toEqual(4);
331-
expect(res[1].groups[0]).toEqual(res[2]);
332-
expect(res[1].groups[1]).toEqual(res[3]);
315+
// group by
316+
const groupResult = DataUtil.group(sorted, state);
317+
const result = groupResult.data;
318+
expect(result.length).toEqual(4);
319+
expect(result[1].groups[0]).toEqual(result[2]);
320+
expect(result[1].groups[1]).toEqual(result[3]);
333321
});
334322
});
335323
}

projects/igniteui-angular/src/lib/data-operations/data-util.ts

Lines changed: 12 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { IFilteringState } from './filtering-state.interface';
22

33
import { IgxSorting, IgxDataRecordSorting } from './sorting-strategy';
4-
import { IGroupByResult, IgxGrouping } from './grouping-strategy';
4+
import { IgxGrouping } from './grouping-strategy';
5+
import { IGroupByResult } from './grouping-result.interface';
56

67
import { IPagingState, PagingError } from './paging-state.interface';
78

@@ -63,56 +64,11 @@ export class DataUtil {
6364
return rec;
6465
}
6566

66-
public static group<T>(data: T[], state: IGroupingState, grid: any = null, groupsRecords: any[] = []): IGroupByResult {
67+
public static group<T>(data: T[], state: IGroupingState, grid: any = null,
68+
groupsRecords: any[] = [], fullResult: IGroupByResult = { data: [], metadata: [] }): IGroupByResult {
6769
const grouping = new IgxGrouping();
6870
groupsRecords.splice(0, groupsRecords.length);
69-
return grouping.groupBy(data, state.expressions, grid, groupsRecords);
70-
}
71-
public static restoreGroups(groupData: IGroupByResult, state: IGroupingState): any[] {
72-
if (state.expressions.length === 0) {
73-
return groupData.data;
74-
}
75-
return this.restoreGroupsIterative(groupData, state);
76-
}
77-
private static restoreGroupsIterative(groupData: IGroupByResult, state: IGroupingState): any[] {
78-
const metadata = groupData.metadata;
79-
const result = [], added = [];
80-
let chain: any[];
81-
let i = 0, j;
82-
let pointer: IGroupByRecord;
83-
let expanded: boolean;
84-
for (i = 0; i < metadata.length;) {
85-
chain = [metadata[i]];
86-
pointer = metadata[i].groupParent;
87-
// break off if the parent is already added
88-
while (pointer && added[0] !== pointer) {
89-
chain.push(pointer);
90-
if (added[0] && added[0].level === pointer.level) {
91-
added.shift();
92-
}
93-
pointer = pointer.groupParent;
94-
}
95-
for (j = chain.length - 1; j >= 0; j--) {
96-
result.push(chain[j]);
97-
added.unshift(chain[j]);
98-
const hierarchy = this.getHierarchy(chain[j]);
99-
const expandState: IGroupByExpandState = state.expansion.find((s) =>
100-
this.isHierarchyMatch(s.hierarchy || [{ fieldName: chain[j].expression.fieldName, value: chain[j].value }], hierarchy));
101-
expanded = expandState ? expandState.expanded : state.defaultExpanded;
102-
if (!expanded) {
103-
break;
104-
}
105-
}
106-
added.shift();
107-
j = Math.max(j, 0);
108-
const start = chain[j].records.findIndex(r => r === groupData.data[i]);
109-
const end = Math.min(metadata.length - i + start, chain[j].records.length);
110-
if (expanded) {
111-
result.push(...chain[j].records.slice(start, end));
112-
}
113-
i += end - start;
114-
}
115-
return result;
71+
return grouping.groupBy(data, state, grid, groupsRecords, fullResult);
11672
}
11773
public static page<T>(data: T[], state: IPagingState): T[] {
11874
if (!state) {
@@ -151,14 +107,20 @@ export class DataUtil {
151107
}
152108
return state.strategy.filter(data, state.expressionsTree);
153109
}
154-
155110
public static treeGridFilter(data: ITreeGridRecord[], state: IFilteringState): ITreeGridRecord[] {
156111
if (!state.strategy) {
157112
state.strategy = new TreeGridFilteringStrategy();
158113
}
159114
return state.strategy.filter(data, state.expressionsTree);
160115
}
161116

117+
public static correctPagingState(state: IPagingState, length: number) {
118+
const maxPage = Math.ceil(length / state.recordsPerPage) - 1;
119+
if (!isNaN(maxPage) && state.index > maxPage) {
120+
state.index = maxPage;
121+
}
122+
}
123+
162124
public static getHierarchy(gRow: IGroupByRecord): Array<IGroupByKey> {
163125
const hierarchy: Array<IGroupByKey> = [];
164126
if (gRow !== undefined && gRow.expression) {

0 commit comments

Comments
 (0)