Skip to content

Commit 5a6bc0b

Browse files
authored
fix(grid): Grouping for columns of dataType date, time and dateTime (#12630)
* fix(grid): groupBy for date, time and dateTime col * chore(*): add line at end * chore(sorting-test): set correct dataType in test
1 parent 28d334f commit 5a6bc0b

File tree

5 files changed

+226
-15
lines changed

5 files changed

+226
-15
lines changed

projects/igniteui-angular/src/lib/grids/common/strategy.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ export class IgxSorting implements IGridSortingStrategy {
4343
while (i < data.length) {
4444
const column = grid ? grid.getColumnByName(expressions[level].fieldName) : null;
4545
const isDate = column?.dataType === DATE_TYPE || column?.dataType === DATE_TIME_TYPE;
46-
const isTime = column?.dataType === TIME_TYPE;
46+
const isTime = column?.dataType === TIME_TYPE || column?.dataType === DATE_TIME_TYPE;
4747
const isString = column?.dataType === STRING_TYPE;
48-
const group = this.groupedRecordsByExpression(data, i, expressions[level], isDate, isString);
48+
const group = this.groupedRecordsByExpression(data, i, expressions[level], isDate, isTime, isString);
4949
const groupRow: IGroupByRecord = {
5050
expression: expressions[level],
5151
level,
@@ -93,11 +93,13 @@ export class IgxSorting implements IGridSortingStrategy {
9393

9494
protected getFieldValue<T>(obj: T, key: string, isDate: boolean = false, isTime: boolean = false) {
9595
let resolvedValue = resolveNestedPath(obj, key);
96-
if (isDate || isTime) {
97-
const date = parseDate(resolvedValue);
98-
resolvedValue = isTime && date ?
99-
new Date().setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()) : date;
100-
96+
const date = parseDate(resolvedValue);
97+
if (date && isDate && isTime) {
98+
resolvedValue = date;
99+
} else if (date && isDate && !isTime) {
100+
resolvedValue = new Date(date.setHours(0, 0, 0, 0));
101+
} else if (date && isTime && !isDate) {
102+
resolvedValue = new Date().setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
101103
}
102104
return resolvedValue;
103105
}
@@ -107,17 +109,18 @@ export class IgxSorting implements IGridSortingStrategy {
107109
index: number,
108110
expression: IGroupingExpression,
109111
isDate: boolean = false,
112+
isTime: boolean = false,
110113
isString: boolean
111114
): T[] {
112115
const res = [];
113116
const key = expression.fieldName;
114117
const len = data.length;
115-
let groupval = this.getFieldValue(data[index], key, isDate);
118+
let groupval = this.getFieldValue(data[index], key, isDate, isTime);
116119
res.push(data[index]);
117120
index++;
118121
const comparer = expression.groupingComparer || DefaultSortingStrategy.instance().compareValues;
119122
for (let i = index; i < len; i++) {
120-
let fieldValue = this.getFieldValue(data[i], key, isDate);
123+
let fieldValue = this.getFieldValue(data[i], key, isDate, isTime);
121124
if (expression.ignoreCase && isString) {
122125
// when column's dataType is string but the value is number
123126
fieldValue = fieldValue?.toString().toLowerCase();
@@ -155,15 +158,15 @@ export class IgxSorting implements IGridSortingStrategy {
155158
}
156159
const column = grid?.getColumnByName(expr.fieldName);
157160
const isDate = column?.dataType === DATE_TYPE || column?.dataType === DATE_TIME_TYPE;
158-
const isTime = column?.dataType === TIME_TYPE;
161+
const isTime = column?.dataType === TIME_TYPE || column?.dataType === DATE_TIME_TYPE;
159162
const isString = column?.dataType === STRING_TYPE;
160163
data = expr.strategy.sort(data, expr.fieldName, expr.dir, expr.ignoreCase, this.getFieldValue, isDate, isTime, grid);
161164
if (expressionIndex === exprsLen - 1) {
162165
return data;
163166
}
164167
// in case of multiple sorting
165168
for (i = 0; i < dataLen; i++) {
166-
gbData = this.groupedRecordsByExpression(data, i, expr, isDate, isString);
169+
gbData = this.groupedRecordsByExpression(data, i, expr, isDate, isTime, isString);
167170
gbDataLen = gbData.length;
168171
if (gbDataLen > 1) {
169172
gbData = this.sortDataRecursive(gbData, expressions, expressionIndex + 1, grid);

projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts

Lines changed: 166 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { IgxChipComponent } from '../../chips/chip.component';
1313
import { wait, UIInteractions } from '../../test-utils/ui-interactions.spec';
1414
import { DefaultSortingStrategy, ISortingExpression, SortingDirection } from '../../data-operations/sorting-strategy';
1515
import { configureTestSuite } from '../../test-utils/configure-suite';
16-
import { DataParent } from '../../test-utils/sample-test-data.spec';
16+
import { DataParent, SampleTestData } from '../../test-utils/sample-test-data.spec';
1717
import { MultiColumnHeadersWithGroupingComponent } from '../../test-utils/grid-samples.spec';
1818
import { GridSelectionFunctions, GridFunctions, GRID_SCROLL_CLASS } from '../../test-utils/grid-functions.spec';
1919
import { GridSelectionMode } from '../common/enums';
@@ -39,7 +39,8 @@ describe('IgxGrid - GroupBy #grid', () => {
3939
GroupByEmptyColumnFieldComponent,
4040
MultiColumnHeadersWithGroupingComponent,
4141
GridGroupByRowCustomSelectorsComponent,
42-
GridGroupByCaseSensitiveComponent
42+
GridGroupByCaseSensitiveComponent,
43+
GridGroupByTestDateTimeDataComponent
4344
],
4445
imports: [NoopAnimationsModule, IgxGridModule]
4546
});
@@ -180,6 +181,145 @@ describe('IgxGrid - GroupBy #grid', () => {
180181
[null, fix.componentInstance.prevDay, fix.componentInstance.today, fix.componentInstance.nextDay]);
181182
}));
182183

184+
it('should only account for year, month and day when grouping by \'date\' dataType column', fakeAsync(() => {
185+
const fix = TestBed.createComponent(GridGroupByTestDateTimeDataComponent);
186+
fix.detectChanges();
187+
188+
const grid = fix.componentInstance.grid;
189+
fix.detectChanges();
190+
191+
grid.groupBy({
192+
fieldName: 'DateField', dir: SortingDirection.Asc, ignoreCase: false
193+
});
194+
fix.detectChanges();
195+
196+
const groupRows = grid.groupsRowList.toArray();
197+
expect(groupRows.length).toEqual(3);
198+
199+
const targetTestVal = new Date(2012, 1, 12);
200+
const index = groupRows.findIndex(gr => new Date(gr.groupRow.value).getTime() === targetTestVal.getTime());
201+
expect(groupRows[index].groupRow.records.length).toEqual(2);
202+
203+
// compare the date values in the target group which are two identical dates with different time values
204+
const field = groupRows[index].groupRow.expression.fieldName;
205+
const record1 = groupRows[index].groupRow.records[0];
206+
const record2 = groupRows[index].groupRow.records[1];
207+
const rec1Date = new Date(record1[field]);
208+
const rec2Date = new Date(record2[field]);
209+
210+
// the time portions of the two records differ, so the Dates are not equal even though they are in the same group
211+
expect(rec1Date.getTime()).not.toEqual(rec2Date.getTime());
212+
// the date portions are the same, so they are in the same group, as the column type is `date`
213+
expect(rec1Date.getDate()).toEqual(rec2Date.getDate());
214+
expect(rec1Date.getMonth()).toEqual(rec2Date.getMonth());
215+
expect(rec1Date.getFullYear()).toEqual(rec2Date.getFullYear());
216+
}));
217+
218+
it('should only account for hours, minutes, seconds and ms when grouping by \'time\' dataType column', fakeAsync(() => {
219+
const fix = TestBed.createComponent(GridGroupByTestDateTimeDataComponent);
220+
fix.detectChanges();
221+
222+
const grid = fix.componentInstance.grid;
223+
224+
grid.groupBy({
225+
fieldName: 'TimeField', dir: SortingDirection.Asc, ignoreCase: false
226+
});
227+
fix.detectChanges();
228+
229+
const groupRows = grid.groupsRowList.toArray();
230+
expect(groupRows.length).toEqual(3);
231+
232+
const targetTestVal = new Date(new Date().setHours(3, 20, 0, 1));
233+
const index = groupRows.findIndex(gr => new Date(gr.groupRow.value).getHours() === targetTestVal.getHours()
234+
&& new Date(gr.groupRow.value).getMinutes() === targetTestVal.getMinutes()
235+
&& new Date(gr.groupRow.value).getSeconds() === targetTestVal.getSeconds()
236+
&& new Date(gr.groupRow.value).getMilliseconds() === targetTestVal.getMilliseconds());
237+
238+
expect(groupRows[index].groupRow.records.length).toEqual(3);
239+
240+
// compare the date values in the target group which are three different dates with same time values
241+
const field = groupRows[index].groupRow.expression.fieldName;
242+
const record1 = groupRows[index].groupRow.records[0];
243+
const record2 = groupRows[index].groupRow.records[1];
244+
const record3 = groupRows[index].groupRow.records[2];
245+
const rec1Date = new Date(record1[field]);
246+
const rec2Date = new Date(record2[field]);
247+
const rec3Date = new Date(record3[field]);
248+
249+
// the date portions of the following records differ, so the Dates are not equal even though they are in the same group
250+
expect(rec1Date.getTime()).not.toEqual(rec2Date.getTime());
251+
// the below are equal by date as well
252+
expect(rec2Date.getTime()).toEqual(rec3Date.getTime());
253+
// the time portions of the not equal dates are the same, so they are in the same group, as the column type is `time`
254+
expect(rec1Date.getHours()).toEqual(rec2Date.getHours());
255+
expect(rec1Date.getMinutes()).toEqual(rec2Date.getMinutes());
256+
expect(rec1Date.getSeconds()).toEqual(rec2Date.getSeconds());
257+
expect(rec1Date.getMilliseconds()).toEqual(rec2Date.getMilliseconds());
258+
}));
259+
260+
it('should account for all date values when grouping by \'dateTime\' dataType column', fakeAsync(() => {
261+
const fix = TestBed.createComponent(GridGroupByTestDateTimeDataComponent);
262+
fix.detectChanges();
263+
264+
const grid = fix.componentInstance.grid;
265+
266+
grid.groupBy({
267+
fieldName: 'DateTimeField', dir: SortingDirection.Asc, ignoreCase: false
268+
});
269+
fix.detectChanges();
270+
271+
// there are two identical DateTime values, so 4 groups out of 5 data records
272+
const groupRows = grid.groupsRowList.toArray();
273+
expect(groupRows.length).toEqual(4);
274+
275+
const targetTestVal = new Date(new Date('2003-03-17').setHours(3, 20, 0, 1));
276+
const index = groupRows.findIndex(gr => new Date(gr.groupRow.value).getTime() === targetTestVal.getTime());
277+
expect(groupRows[index].groupRow.records.length).toEqual(2);
278+
279+
// compare the date values in the target group which are two identical dates - date and time portions
280+
const field = groupRows[index].groupRow.expression.fieldName;
281+
const record1 = groupRows[index].groupRow.records[0];
282+
const record2 = groupRows[index].groupRow.records[1];
283+
const rec1Date = new Date(record1[field]);
284+
const rec2Date = new Date(record2[field]);
285+
286+
expect(rec1Date.getTime()).toEqual(rec2Date.getTime());
287+
}));
288+
289+
it('should display time value in the group by row when grouped by a \'time\' column', fakeAsync(() => {
290+
const fix = TestBed.createComponent(GridGroupByTestDateTimeDataComponent);
291+
fix.detectChanges();
292+
293+
const grid = fix.componentInstance.grid;
294+
295+
grid.groupBy({
296+
fieldName: 'TimeField', dir: SortingDirection.Asc, ignoreCase: false
297+
});
298+
fix.detectChanges();
299+
300+
const groupRows = grid.groupsRowList.toArray();
301+
const expectedValue1 = groupRows[0].nativeElement.nextElementSibling.querySelectorAll('igx-grid-cell')[3].textContent;
302+
const actualValue1 = groupRows[0].element.nativeElement.querySelector('.igx-group-label__text').textContent;
303+
expect(expectedValue1).toEqual(actualValue1);
304+
}));
305+
306+
it('should display time value in the group by row when grouped by a \'dateTime\' column', fakeAsync(() => {
307+
const fix = TestBed.createComponent(GridGroupByTestDateTimeDataComponent);
308+
fix.detectChanges();
309+
310+
const grid = fix.componentInstance.grid;
311+
312+
grid.groupBy({
313+
fieldName: 'DateTimeField', dir: SortingDirection.Asc, ignoreCase: false
314+
});
315+
fix.detectChanges();
316+
317+
const groupRows = grid.groupsRowList.toArray();
318+
const expectedValue1 = groupRows[0].nativeElement.nextElementSibling.querySelectorAll('igx-grid-cell')[4].textContent;
319+
const actualValue1 = groupRows[0].element.nativeElement.querySelector('.igx-group-label__text').textContent;
320+
expect(expectedValue1).toEqual(actualValue1);
321+
}));
322+
183323
it('should allow grouping by multiple columns.', fakeAsync(() => {
184324
const fix = TestBed.createComponent(DefaultGridComponent);
185325
fix.detectChanges();
@@ -3816,3 +3956,27 @@ export class GridGroupByCaseSensitiveComponent {
38163956
}
38173957
];
38183958
}
3959+
3960+
@Component({
3961+
template: `
3962+
<igx-grid
3963+
#grid
3964+
[width]='width'
3965+
[height]='height'
3966+
[data]="datesData">
3967+
<igx-column [field]="'ProductID'" [width]="200" [groupable]="true" dataType="number"></igx-column>
3968+
<igx-column [field]="'ProductName'" [width]="200" [groupable]="true" dataType="string"></igx-column>
3969+
<igx-column [field]="'DateField'" [width]="200" [groupable]="true" dataType="date"></igx-column>
3970+
<igx-column [field]="'TimeField'" [width]="200" [groupable]="true" dataType="time"></igx-column>
3971+
<igx-column [field]="'DateTimeField'" [width]="200" [groupable]="true" dataType="dateTime"></igx-column>
3972+
</igx-grid>
3973+
`
3974+
})
3975+
export class GridGroupByTestDateTimeDataComponent {
3976+
@ViewChild("grid", { read: IgxGridComponent, static: true })
3977+
public grid: IgxGridComponent;
3978+
3979+
public width = '800px';
3980+
public height = null;
3981+
public datesData = SampleTestData.generateTestDateTimeData();
3982+
}

projects/igniteui-angular/src/lib/grids/grid/grid.sorting.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ describe('IgxGrid - Grid Sorting #grid', () => {
163163
});
164164
fixture.detectChanges();
165165
grid = fixture.componentInstance.grid;
166+
const hireDateCol = grid.columns.findIndex(col => col.field === "HireDate");
167+
grid.columns[hireDateCol].dataType = 'dateTime';
168+
fixture.detectChanges();
166169

167170
const currentColumn = 'HireDate';
168171
grid.sort({ fieldName: currentColumn, dir: SortingDirection.Asc, ignoreCase: false });

projects/igniteui-angular/src/lib/grids/grid/groupby-row.component.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@
5555
<ng-container *ngIf="dataType === 'number'">
5656
<span class="igx-group-label__text">{{ groupRow.value | number }}</span>
5757
</ng-container>
58-
<ng-container *ngIf="dataType === 'date'">
59-
<span class="igx-group-label__text">{{ groupRow.value | date }}</span>
58+
<ng-container *ngIf="dataType === 'date' || dataType === 'dateTime' || dataType === 'time'">
59+
<span class="igx-group-label__text">{{ groupRow.value |
60+
date:groupRow.column.pipeArgs.format:groupRow.column.pipeArgs.timezone:grid.locale }}</span>
6061
</ng-container>
6162
</ng-template>
6263

projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2499,6 +2499,46 @@ export class SampleTestData {
24992499
return prods;
25002500
}
25012501

2502+
public static generateTestDateTimeData = () => {
2503+
return [
2504+
{
2505+
ProductID: 1,
2506+
ProductName: 'Product1',
2507+
DateField: new Date('2012-02-12'),
2508+
TimeField: new Date(new Date('2012-02-12').setHours(3, 20, 0, 1)),
2509+
DateTimeField: new Date(new Date('2003-03-17').setHours(3, 20, 5)),
2510+
},
2511+
{
2512+
ProductID: 2,
2513+
ProductName: 'Product2',
2514+
DateField: new Date('2012-02-13'),
2515+
TimeField: new Date(new Date('2003-03-17').setHours(3, 20, 0, 1)),
2516+
DateTimeField: new Date(new Date('2003-03-17').setHours(3, 20)),
2517+
},
2518+
{
2519+
ProductID: 3,
2520+
ProductName: 'Product3',
2521+
DateField: new Date('2012-02-12').setHours(1, 55),
2522+
TimeField: new Date(new Date('2012-02-12').setHours(4, 4)),
2523+
DateTimeField: new Date(new Date('2006-03-17').setHours(1, 55)),
2524+
},
2525+
{
2526+
ProductID: 4,
2527+
ProductName: 'Product4',
2528+
DateField: new Date(new Date('2006-03-17').setHours(11, 11)),
2529+
TimeField: new Date(new Date('2006-03-17').setHours(11, 11)),
2530+
DateTimeField: new Date(new Date('2003-03-17').setHours(3, 20, 0, 1)),
2531+
},
2532+
{
2533+
ProductID: 5,
2534+
ProductName: 'Product5',
2535+
DateField: new Date(new Date('2006-03-17').setHours(11, 11)),
2536+
TimeField: new Date(new Date('2003-03-17').setHours(3, 20, 0, 1)),
2537+
DateTimeField: new Date(new Date('2003-03-17').setHours(3, 20, 0, 1)),
2538+
}
2539+
];
2540+
};
2541+
25022542
/* Gets the name of the identifier column if exists. */
25032543
private static getIDColumnName(dataItem: any) {
25042544
if (!dataItem) {

0 commit comments

Comments
 (0)