Skip to content

Commit 0713cd3

Browse files
mvgalievignatvilesov
authored andcommitted
fixed april list of bugs (#108)
1 parent 92cc9d1 commit 0713cd3

File tree

6 files changed

+206
-30
lines changed

6 files changed

+206
-30
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.11.0
2+
* Fixed issue with wrong tooltips
3+
* Fixed issue with strange category labels
4+
* Fixed issue with unsynchronized legend and task color
15
## 1.10.1
26
* Added 'End date' field to tooltip
37
* Fixed issue with date in extra information field

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "powerbi-visuals-gantt",
3-
"version": "1.10.1",
3+
"version": "1.11.0",
44
"description": "A Gantt chart is a type of bar chart which illustrates a project timeline or schedule. The Gantt Chart visual shows the Tasks, Start Dates, Durations, % Complete, and Resources for a project. The Gantt Chart visual can be used to show current schedule status using percent-complete shadings and a vertical \"TODAY\" line. The Legend may be used to group or filter tasks based upon data values.",
55
"repository": {
66
"type": "git",

pbiviz.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"displayName": "Gantt",
55
"guid": "Gantt1448688115699",
66
"visualClassName": "Gantt",
7-
"version": "1.10.1",
7+
"version": "1.11.0",
88
"description": "A Gantt chart is a type of bar chart which illustrates a project timeline or schedule. The Gantt Chart visual shows the Tasks, Start Dates, Durations, % Complete, and Resources for a project. The Gantt Chart visual can be used to show current schedule status using percent-complete shadings and a vertical \"TODAY\" line. The Legend may be used to group or filter tasks based upon data values.",
99
"supportUrl": "http://community.powerbi.com",
1010
"gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-gantt"

src/gantt.ts

+37-8
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ module powerbi.extensibility.visual {
553553
* @param formatters Formatting options for gantt attributes.
554554
* @param durationUnit Duration unit option
555555
*/
556-
private static getTooltipInfo(
556+
public static getTooltipInfo(
557557
task: Task,
558558
formatters: GanttChartFormatters,
559559
durationUnit: string): VisualTooltipDataItem[] {
@@ -619,6 +619,10 @@ module powerbi.extensibility.visual {
619619
})
620620
.forEach(tooltip => tooltipDataArray.push(tooltip));
621621

622+
tooltipDataArray
623+
.filter(x => x.value && typeof x.value !== "string")
624+
.forEach(tooltip => tooltip.value = tooltip.value.toString());
625+
622626
return tooltipDataArray;
623627
}
624628

@@ -703,13 +707,11 @@ module powerbi.extensibility.visual {
703707
let color: string = settings.taskConfig.fill;
704708

705709
if (!useDefaultColor) {
706-
color = (taskTypes.types.length <= 1)
707-
? settings.taskConfig.fill
708-
: colorHelper.getColorForMeasure(typeMeta.columnGroup.objects, typeMeta.name);
710+
color = colorHelper.getColorForMeasure(typeMeta.columnGroup.objects, typeMeta.name);
709711
}
710712

711713
return {
712-
label: typeMeta.name as string,
714+
label: typeMeta.name,
713715
color: color,
714716
icon: LegendIcon.Circle,
715717
selected: false,
@@ -1205,6 +1207,11 @@ module powerbi.extensibility.visual {
12051207
: null;
12061208

12071209
const tasks: Task[] = Gantt.createTasks(dataView, taskTypes, host, formatters, colors, settings, taskColor);
1210+
1211+
// Remove empty legend if tasks isn't exist
1212+
const types = _.groupBy(tasks, x => x.taskType);
1213+
legendData.dataPoints = legendData.dataPoints.filter(x => types[x.label]);
1214+
12081215
return {
12091216
dataView,
12101217
settings,
@@ -1297,6 +1304,26 @@ module powerbi.extensibility.visual {
12971304
break;
12981305
}
12991306
}
1307+
/**
1308+
* Delete parent group names from tasks if this parent tasks do not representated in dataset
1309+
* @param tasks
1310+
* @param task
1311+
*/
1312+
public static deleteNonExistentParents(tasks: Task[], task: Task): Task {
1313+
const parentNames = task.parent.split(".").filter((name) => name !== task.name);
1314+
let taskNames = tasks.map((task) => task.name);
1315+
let newTaskParent = "";
1316+
1317+
parentNames.forEach((parentName) => {
1318+
if (taskNames.indexOf(parentName) !== -1) {
1319+
newTaskParent += parentName + ".";
1320+
}
1321+
});
1322+
newTaskParent += task.name;
1323+
task.parent = newTaskParent;
1324+
1325+
return task;
1326+
}
13001327

13011328
/**
13021329
* Called on data change or resizing
@@ -1319,11 +1346,13 @@ module powerbi.extensibility.visual {
13191346

13201347
this.renderLegend();
13211348
this.updateChartSize();
1322-
let tasks: Task[] = this.viewModel.tasks
1323-
.filter((task: Task) => task.visibility)
1349+
1350+
const visibleTasks = this.viewModel.tasks
1351+
.filter((task: Task) => task.visibility);
1352+
const tasks: Task[] = visibleTasks
13241353
.map((task: Task, i: number) => {
13251354
task.id = i;
1326-
return task;
1355+
return Gantt.deleteNonExistentParents(visibleTasks, task);
13271356
});
13281357

13291358
if (this.interactivityService) {

test/visualBuilder.ts

+92
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,18 @@ module powerbi.extensibility.visual.test {
3030
// powerbi.extensibility.utils.test
3131
import VisualBuilderBase = powerbi.extensibility.utils.test.VisualBuilderBase;
3232
import getRandomNumber = powerbi.extensibility.utils.test.helpers.getRandomNumber;
33+
import Task = powerbi.extensibility.visual.Gantt1448688115699.Task;
3334

3435
// Gantt1448688115699
3536
import VisualClass = powerbi.extensibility.visual.Gantt1448688115699.Gantt;
3637

38+
interface TaskMockParamsInterface {
39+
id: number;
40+
name: string;
41+
parent: string;
42+
children: string[];
43+
}
44+
3745
export class GanttBuilder extends VisualBuilderBase<VisualClass> {
3846
constructor(width: number, height: number) {
3947
super(width, height, "Gantt1448688115699");
@@ -149,6 +157,90 @@ module powerbi.extensibility.visual.test {
149157
return { solid: { color: color } };
150158
}
151159

160+
public static getTasksMockData(mockArray: object, mockCaseName: string): Task[] {
161+
return mockArray[mockCaseName]["data"];
162+
}
163+
164+
public static getTaskMockExpected(mockArray: object, mockCaseName: string): Task[] {
165+
return mockArray[mockCaseName]["expected"];
166+
}
167+
168+
private static generateTaskWithDefaultParams(taskMockParams: TaskMockParamsInterface) {
169+
return {
170+
id: taskMockParams.id,
171+
name: taskMockParams.name,
172+
start: new Date(),
173+
duration: 1,
174+
completion: 1,
175+
resource: "res",
176+
end: new Date(),
177+
parent: taskMockParams.parent,
178+
children: taskMockParams.children,
179+
visibility: true,
180+
taskType: "type",
181+
description: name,
182+
color: "red",
183+
tooltipInfo: [],
184+
extraInformation: [],
185+
daysOffList: [],
186+
wasDowngradeDurationUnit: true,
187+
stepDurationTransformation: 0
188+
};
189+
}
190+
191+
private static generateMocksCase(taskMockParams: TaskMockParamsInterface[]) {
192+
let result = taskMockParams.map((taskMockParamsItem) => {
193+
return GanttBuilder.generateTaskWithDefaultParams(taskMockParamsItem);
194+
});
195+
196+
return result;
197+
}
198+
199+
public static getTaskMockCommon() {
200+
let taskMock = {
201+
taskWithCorrectParentsMock: {
202+
"data": GanttBuilder.generateMocksCase([
203+
{id: 1, name: "T1", parent: "T1", children: []},
204+
{id: 2, name: "Group C", parent: "Group C", children: ["T2"]},
205+
{id: 3, name: "T2", parent: "Group C.T2", children: []}
206+
]),
207+
"expected" : GanttBuilder.generateMocksCase([
208+
{id: 1, name: "T1", parent: "T1", children: []},
209+
{id: 2, name: "Group C", parent: "Group C", children: ["T2"]},
210+
{id: 3, name: "T2", parent: "Group C.T2", children: []}
211+
])
212+
},
213+
taskWithNotExistentParentsMock: {
214+
"data": GanttBuilder.generateMocksCase([
215+
{id: 1, name: "T1", parent: "T1", children: []},
216+
{id: 2, name: "Group C", parent: "Group C", children: []},
217+
{id: 3, name: "T2", parent: "Group A.T2", children: []},
218+
{id: 4, name: "T3", parent: "Group B.T3", children: []}
219+
]),
220+
"expected" : GanttBuilder.generateMocksCase([
221+
{id: 1, name: "T1", parent: "T1", children: []},
222+
{id: 2, name: "Group C", parent: "Group C", children: []},
223+
{id: 3, name: "T2", parent: "T2", children: []},
224+
{id: 4, name: "T3", parent: "T3", children: []}
225+
])
226+
},
227+
taskWithNotExistentMiddleParentsMock: {
228+
"data": GanttBuilder.generateMocksCase([
229+
{id: 1, name: "T1", parent: "T1", children: []},
230+
{id: 2, name: "Group C", parent: "Group C", children: ["T2"]},
231+
{id: 3, name: "T2", parent: "Group C.Group M.T2", children: []}
232+
]),
233+
"expected" : GanttBuilder.generateMocksCase([
234+
{id: 1, name: "T1", parent: "T1", children: []},
235+
{id: 2, name: "Group C", parent: "Group C", children: ["T2"]},
236+
{id: 3, name: "T2", parent: "Group C.T2", children: []}
237+
])
238+
}
239+
};
240+
241+
return taskMock;
242+
}
243+
152244
public static getRandomHexColor(): string {
153245
return GanttBuilder.getHexColorFromNumber(GanttBuilder.getRandomInteger(0, 16777215 + 1));
154246
}

test/visualTest.ts

+71-20
Original file line numberDiff line numberDiff line change
@@ -406,28 +406,32 @@ module powerbi.extensibility.visual.test {
406406
});
407407
});
408408

409+
it("Verify tooltips have only string values", (done) => {
410+
const randomNumber = 134223;
411+
const durationUnit = "day";
412+
413+
const task: any = {
414+
taskType: randomNumber,
415+
name: randomNumber,
416+
start: new Date(),
417+
end: new Date(),
418+
duration: randomNumber,
419+
completion: randomNumber,
420+
extraInformation: []
421+
};
409422

410-
it("Verify tooltips have tooltips", (done) => {
411-
dataView = defaultDataViewBuilder.getDataView([
412-
GanttData.ColumnTask,
413-
GanttData.ColumnStartDate,
414-
GanttData.ColumnDuration,
415-
GanttData.ColumnTooltips]);
416-
417-
visualBuilder.updateRenderTimeout(dataView, () => {
418-
let tasks = d3.select(visualBuilder.element.get(0)).selectAll(".task").data();
419-
let index = 0;
420-
for (let task of tasks) {
421-
for (let tooltipInfo of task.tooltipInfo) {
422-
if (tooltipInfo.displayName === GanttData.ColumnTooltips) {
423-
let value: VisualTooltipDataItem = tooltipInfo.value;
424-
expect(value).toEqual(defaultDataViewBuilder.valuesTooltips[index++]);
425-
}
426-
}
427-
}
423+
const formatters = {
424+
startDateFormatter: jasmine.createSpyObj("startDateFormatter", ["format"]),
425+
completionFormatter: jasmine.createSpyObj("completionFormatter", ["format"])
426+
};
428427

429-
done();
430-
});
428+
const tooltips = VisualClass.getTooltipInfo(task, formatters, durationUnit);
429+
tooltips
430+
.filter(t => t.value !== null && t.value !== undefined)
431+
.forEach(t => {
432+
expect(typeof t.value).toBe("string");
433+
});
434+
done();
431435
});
432436

433437
it("Verify sub tasks", (done) => {
@@ -1192,6 +1196,53 @@ module powerbi.extensibility.visual.test {
11921196
});
11931197
});
11941198

1199+
describe("check not existent parent deletion from tasks", () => {
1200+
it("check on correct parent dataset", () => {
1201+
let taskMock = GanttBuilder.getTaskMockCommon(),
1202+
data = GanttBuilder.getTasksMockData(taskMock, "taskWithCorrectParentsMock"),
1203+
expectedResult = GanttBuilder.getTaskMockExpected(taskMock, "taskWithCorrectParentsMock"),
1204+
realResult: Task[] = data.map((task) => VisualClass.deleteNonExistentParents(data, task));
1205+
1206+
expectSimilarObject(expectedResult, realResult);
1207+
});
1208+
1209+
it("check on not existent parent dataset - parent.children", () => {
1210+
let taskMock = GanttBuilder.getTaskMockCommon(),
1211+
data = GanttBuilder.getTasksMockData(taskMock, "taskWithNotExistentParentsMock"),
1212+
expectedResult = GanttBuilder.getTaskMockExpected(taskMock, "taskWithNotExistentParentsMock"),
1213+
realResult: Task[] = data.map((task) => VisualClass.deleteNonExistentParents(data, task));
1214+
1215+
expectSimilarObject(expectedResult, realResult);
1216+
});
1217+
1218+
it("check on not existent in the middle parent dataset", () => {
1219+
// parent.nonExistentParent.children -> parent.children
1220+
let taskMock = GanttBuilder.getTaskMockCommon(),
1221+
data = GanttBuilder.getTasksMockData(taskMock, "taskWithNotExistentMiddleParentsMock"),
1222+
expectedResult = GanttBuilder.getTaskMockExpected(taskMock, "taskWithNotExistentMiddleParentsMock"),
1223+
realResult: Task[] = data.map((task) => VisualClass.deleteNonExistentParents(data, task));
1224+
1225+
expectSimilarObject(expectedResult, realResult);
1226+
});
1227+
1228+
function expectSimilarObject(expectedResult, realResult) {
1229+
const expectedKeys = Object.keys(expectedResult);
1230+
const realKeys = Object.keys(realResult);
1231+
1232+
expect(expectedKeys.length).toEqual(realKeys.length);
1233+
expectedKeys.forEach(key => {
1234+
const expectedObj = expectedResult[key];
1235+
const realObj = realResult[key];
1236+
1237+
if (typeof expectedObj === "object") {
1238+
expectSimilarObject(expectedObj, realObj);
1239+
} else {
1240+
expect(realObj).toEqual(expectedObj);
1241+
}
1242+
});
1243+
};
1244+
});
1245+
11951246
describe("Task Settings", () => {
11961247
it("color", (done) => {
11971248
dataView = defaultDataViewBuilder.getDataView([

0 commit comments

Comments
 (0)