Skip to content

Commit

Permalink
fixed april list of bugs (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
mvgaliev authored and ignatvilesov committed Apr 4, 2018
1 parent 92cc9d1 commit 0713cd3
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 30 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.11.0
* Fixed issue with wrong tooltips
* Fixed issue with strange category labels
* Fixed issue with unsynchronized legend and task color
## 1.10.1
* Added 'End date' field to tooltip
* Fixed issue with date in extra information field
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "powerbi-visuals-gantt",
"version": "1.10.1",
"version": "1.11.0",
"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.",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion pbiviz.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"displayName": "Gantt",
"guid": "Gantt1448688115699",
"visualClassName": "Gantt",
"version": "1.10.1",
"version": "1.11.0",
"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.",
"supportUrl": "http://community.powerbi.com",
"gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-gantt"
Expand Down
45 changes: 37 additions & 8 deletions src/gantt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ module powerbi.extensibility.visual {
* @param formatters Formatting options for gantt attributes.
* @param durationUnit Duration unit option
*/
private static getTooltipInfo(
public static getTooltipInfo(
task: Task,
formatters: GanttChartFormatters,
durationUnit: string): VisualTooltipDataItem[] {
Expand Down Expand Up @@ -619,6 +619,10 @@ module powerbi.extensibility.visual {
})
.forEach(tooltip => tooltipDataArray.push(tooltip));

tooltipDataArray
.filter(x => x.value && typeof x.value !== "string")
.forEach(tooltip => tooltip.value = tooltip.value.toString());

return tooltipDataArray;
}

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

if (!useDefaultColor) {
color = (taskTypes.types.length <= 1)
? settings.taskConfig.fill
: colorHelper.getColorForMeasure(typeMeta.columnGroup.objects, typeMeta.name);
color = colorHelper.getColorForMeasure(typeMeta.columnGroup.objects, typeMeta.name);
}

return {
label: typeMeta.name as string,
label: typeMeta.name,
color: color,
icon: LegendIcon.Circle,
selected: false,
Expand Down Expand Up @@ -1205,6 +1207,11 @@ module powerbi.extensibility.visual {
: null;

const tasks: Task[] = Gantt.createTasks(dataView, taskTypes, host, formatters, colors, settings, taskColor);

// Remove empty legend if tasks isn't exist
const types = _.groupBy(tasks, x => x.taskType);
legendData.dataPoints = legendData.dataPoints.filter(x => types[x.label]);

return {
dataView,
settings,
Expand Down Expand Up @@ -1297,6 +1304,26 @@ module powerbi.extensibility.visual {
break;
}
}
/**
* Delete parent group names from tasks if this parent tasks do not representated in dataset
* @param tasks
* @param task
*/
public static deleteNonExistentParents(tasks: Task[], task: Task): Task {
const parentNames = task.parent.split(".").filter((name) => name !== task.name);
let taskNames = tasks.map((task) => task.name);
let newTaskParent = "";

parentNames.forEach((parentName) => {
if (taskNames.indexOf(parentName) !== -1) {
newTaskParent += parentName + ".";
}
});
newTaskParent += task.name;
task.parent = newTaskParent;

return task;
}

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

this.renderLegend();
this.updateChartSize();
let tasks: Task[] = this.viewModel.tasks
.filter((task: Task) => task.visibility)

const visibleTasks = this.viewModel.tasks
.filter((task: Task) => task.visibility);
const tasks: Task[] = visibleTasks
.map((task: Task, i: number) => {
task.id = i;
return task;
return Gantt.deleteNonExistentParents(visibleTasks, task);
});

if (this.interactivityService) {
Expand Down
92 changes: 92 additions & 0 deletions test/visualBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,18 @@ module powerbi.extensibility.visual.test {
// powerbi.extensibility.utils.test
import VisualBuilderBase = powerbi.extensibility.utils.test.VisualBuilderBase;
import getRandomNumber = powerbi.extensibility.utils.test.helpers.getRandomNumber;
import Task = powerbi.extensibility.visual.Gantt1448688115699.Task;

// Gantt1448688115699
import VisualClass = powerbi.extensibility.visual.Gantt1448688115699.Gantt;

interface TaskMockParamsInterface {
id: number;
name: string;
parent: string;
children: string[];
}

export class GanttBuilder extends VisualBuilderBase<VisualClass> {
constructor(width: number, height: number) {
super(width, height, "Gantt1448688115699");
Expand Down Expand Up @@ -149,6 +157,90 @@ module powerbi.extensibility.visual.test {
return { solid: { color: color } };
}

public static getTasksMockData(mockArray: object, mockCaseName: string): Task[] {
return mockArray[mockCaseName]["data"];
}

public static getTaskMockExpected(mockArray: object, mockCaseName: string): Task[] {
return mockArray[mockCaseName]["expected"];
}

private static generateTaskWithDefaultParams(taskMockParams: TaskMockParamsInterface) {
return {
id: taskMockParams.id,
name: taskMockParams.name,
start: new Date(),
duration: 1,
completion: 1,
resource: "res",
end: new Date(),
parent: taskMockParams.parent,
children: taskMockParams.children,
visibility: true,
taskType: "type",
description: name,
color: "red",
tooltipInfo: [],
extraInformation: [],
daysOffList: [],
wasDowngradeDurationUnit: true,
stepDurationTransformation: 0
};
}

private static generateMocksCase(taskMockParams: TaskMockParamsInterface[]) {
let result = taskMockParams.map((taskMockParamsItem) => {
return GanttBuilder.generateTaskWithDefaultParams(taskMockParamsItem);
});

return result;
}

public static getTaskMockCommon() {
let taskMock = {
taskWithCorrectParentsMock: {
"data": GanttBuilder.generateMocksCase([
{id: 1, name: "T1", parent: "T1", children: []},
{id: 2, name: "Group C", parent: "Group C", children: ["T2"]},
{id: 3, name: "T2", parent: "Group C.T2", children: []}
]),
"expected" : GanttBuilder.generateMocksCase([
{id: 1, name: "T1", parent: "T1", children: []},
{id: 2, name: "Group C", parent: "Group C", children: ["T2"]},
{id: 3, name: "T2", parent: "Group C.T2", children: []}
])
},
taskWithNotExistentParentsMock: {
"data": GanttBuilder.generateMocksCase([
{id: 1, name: "T1", parent: "T1", children: []},
{id: 2, name: "Group C", parent: "Group C", children: []},
{id: 3, name: "T2", parent: "Group A.T2", children: []},
{id: 4, name: "T3", parent: "Group B.T3", children: []}
]),
"expected" : GanttBuilder.generateMocksCase([
{id: 1, name: "T1", parent: "T1", children: []},
{id: 2, name: "Group C", parent: "Group C", children: []},
{id: 3, name: "T2", parent: "T2", children: []},
{id: 4, name: "T3", parent: "T3", children: []}
])
},
taskWithNotExistentMiddleParentsMock: {
"data": GanttBuilder.generateMocksCase([
{id: 1, name: "T1", parent: "T1", children: []},
{id: 2, name: "Group C", parent: "Group C", children: ["T2"]},
{id: 3, name: "T2", parent: "Group C.Group M.T2", children: []}
]),
"expected" : GanttBuilder.generateMocksCase([
{id: 1, name: "T1", parent: "T1", children: []},
{id: 2, name: "Group C", parent: "Group C", children: ["T2"]},
{id: 3, name: "T2", parent: "Group C.T2", children: []}
])
}
};

return taskMock;
}

public static getRandomHexColor(): string {
return GanttBuilder.getHexColorFromNumber(GanttBuilder.getRandomInteger(0, 16777215 + 1));
}
Expand Down
91 changes: 71 additions & 20 deletions test/visualTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,28 +406,32 @@ module powerbi.extensibility.visual.test {
});
});

it("Verify tooltips have only string values", (done) => {
const randomNumber = 134223;
const durationUnit = "day";

const task: any = {
taskType: randomNumber,
name: randomNumber,
start: new Date(),
end: new Date(),
duration: randomNumber,
completion: randomNumber,
extraInformation: []
};

it("Verify tooltips have tooltips", (done) => {
dataView = defaultDataViewBuilder.getDataView([
GanttData.ColumnTask,
GanttData.ColumnStartDate,
GanttData.ColumnDuration,
GanttData.ColumnTooltips]);

visualBuilder.updateRenderTimeout(dataView, () => {
let tasks = d3.select(visualBuilder.element.get(0)).selectAll(".task").data();
let index = 0;
for (let task of tasks) {
for (let tooltipInfo of task.tooltipInfo) {
if (tooltipInfo.displayName === GanttData.ColumnTooltips) {
let value: VisualTooltipDataItem = tooltipInfo.value;
expect(value).toEqual(defaultDataViewBuilder.valuesTooltips[index++]);
}
}
}
const formatters = {
startDateFormatter: jasmine.createSpyObj("startDateFormatter", ["format"]),
completionFormatter: jasmine.createSpyObj("completionFormatter", ["format"])
};

done();
});
const tooltips = VisualClass.getTooltipInfo(task, formatters, durationUnit);
tooltips
.filter(t => t.value !== null && t.value !== undefined)
.forEach(t => {
expect(typeof t.value).toBe("string");
});
done();
});

it("Verify sub tasks", (done) => {
Expand Down Expand Up @@ -1192,6 +1196,53 @@ module powerbi.extensibility.visual.test {
});
});

describe("check not existent parent deletion from tasks", () => {
it("check on correct parent dataset", () => {
let taskMock = GanttBuilder.getTaskMockCommon(),
data = GanttBuilder.getTasksMockData(taskMock, "taskWithCorrectParentsMock"),
expectedResult = GanttBuilder.getTaskMockExpected(taskMock, "taskWithCorrectParentsMock"),
realResult: Task[] = data.map((task) => VisualClass.deleteNonExistentParents(data, task));

expectSimilarObject(expectedResult, realResult);
});

it("check on not existent parent dataset - parent.children", () => {
let taskMock = GanttBuilder.getTaskMockCommon(),
data = GanttBuilder.getTasksMockData(taskMock, "taskWithNotExistentParentsMock"),
expectedResult = GanttBuilder.getTaskMockExpected(taskMock, "taskWithNotExistentParentsMock"),
realResult: Task[] = data.map((task) => VisualClass.deleteNonExistentParents(data, task));

expectSimilarObject(expectedResult, realResult);
});

it("check on not existent in the middle parent dataset", () => {
// parent.nonExistentParent.children -> parent.children
let taskMock = GanttBuilder.getTaskMockCommon(),
data = GanttBuilder.getTasksMockData(taskMock, "taskWithNotExistentMiddleParentsMock"),
expectedResult = GanttBuilder.getTaskMockExpected(taskMock, "taskWithNotExistentMiddleParentsMock"),
realResult: Task[] = data.map((task) => VisualClass.deleteNonExistentParents(data, task));

expectSimilarObject(expectedResult, realResult);
});

function expectSimilarObject(expectedResult, realResult) {
const expectedKeys = Object.keys(expectedResult);
const realKeys = Object.keys(realResult);

expect(expectedKeys.length).toEqual(realKeys.length);
expectedKeys.forEach(key => {
const expectedObj = expectedResult[key];
const realObj = realResult[key];

if (typeof expectedObj === "object") {
expectSimilarObject(expectedObj, realObj);
} else {
expect(realObj).toEqual(expectedObj);
}
});
};
});

describe("Task Settings", () => {
it("color", (done) => {
dataView = defaultDataViewBuilder.getDataView([
Expand Down

0 comments on commit 0713cd3

Please sign in to comment.