Skip to content

Commit

Permalink
Merge pull request #101 from melfore/94-tasks-task-duration
Browse files Browse the repository at this point in the history
[Tasks] Refactored filter function with errors
  • Loading branch information
luciob authored Sep 25, 2023
2 parents aa1ddf9 + f6c0635 commit 55791e8
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { Resource } from "./resources/utils/resources";
export { TaskData } from "./tasks/utils/tasks";
export { KonvaTimelineError } from "./utils/operations";
export { TimeRange } from "./utils/time-range";
export { RESOLUTIONS, Resolution } from "./utils/time-resolution";

Expand Down
92 changes: 92 additions & 0 deletions src/tasks/utils/tasks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { DateTime, Interval } from "luxon";

import { TimeRange } from "../..";
import { generateStoryData } from "../../KonvaTimeline/stories-data";

import { filterTasks } from "./tasks";

const range: TimeRange = { start: 1672527600000, end: 1672614000000 };

const interval = Interval.fromDateTimes(DateTime.fromMillis(range.start), DateTime.fromMillis(range.end));

describe("filterTasks", () => {
it("empty", () => {
const tasks = filterTasks([], interval);
expect(tasks).toEqual({
errors: [{ entity: "task", level: "warn", message: "No data" }],
items: [],
});
});

it("task invalid", () => {
const tasks = filterTasks(
[{ id: "1", label: "Task #1", resourceId: "1", time: { start: 1672578000000, end: 1672563600000 } }],
interval
);

expect(tasks).toEqual({
errors: [{ entity: "task", level: "error", message: "Invalid time", refId: "1" }],
items: [],
});
});

it("task out of interval", () => {
const tasks = filterTasks(
[{ id: "1", label: "Task #1", resourceId: "1", time: { start: 1672470000000, end: 1672477200000 } }],
interval
);

expect(tasks).toEqual({
errors: [{ entity: "task", level: "warn", message: "Outside interval", refId: "1" }],
items: [],
});
});

it("valid", () => {
const tasks = filterTasks(
[{ id: "1", label: "Task #1", resourceId: "1", time: { start: 1672556400000, end: 1672578000000 } }],
interval
);

expect(tasks).toEqual({
errors: [],
items: [{ id: "1", label: "Task #1", resourceId: "1", time: { start: 1672556400000, end: 1672578000000 } }],
});
});

it("mixed", () => {
const tasks = filterTasks(
[
{ id: "1", label: "Task #1", resourceId: "1", time: { start: 1672556400000, end: 1672578000000 } },
{ id: "2", label: "Task #2", resourceId: "1", time: { start: 1672470000000, end: 1672477200000 } },
{ id: "3", label: "Task #3", resourceId: "1", time: { start: 1672578000000, end: 1672563600000 } },
],
interval
);

expect(tasks).toEqual({
errors: [
{ entity: "task", level: "warn", message: "Outside interval", refId: "2" },
{ entity: "task", level: "error", message: "Invalid time", refId: "3" },
],
items: [{ id: "1", label: "Task #1", resourceId: "1", time: { start: 1672556400000, end: 1672578000000 } }],
});
});

it("bulk - monthly", () => {
const { range, tasks: allTasks } = generateStoryData({
averageTaskDurationInMinutes: 10,
resourcesCount: 16,
tasksCount: 100000,
timeRangeInDays: 60,
});

const start = new Date().valueOf();
filterTasks(allTasks, Interval.fromDateTimes(DateTime.fromMillis(range.start), DateTime.fromMillis(range.end)));
const end = new Date().valueOf();
const operationLength = end - start;
console.log(`Filter tasks: ${operationLength} ms`);

expect(operationLength).toBeLessThan(100);
});
});
41 changes: 33 additions & 8 deletions src/tasks/utils/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Interval } from "luxon";

import { TimeRange, toInterval } from "../../utils/time-range";
import { KonvaTimelineError, Operation } from "../../utils/operations";
import { TimeRange } from "../../utils/time-range";

export interface TaskData {
/**
Expand All @@ -21,13 +22,37 @@ export interface TaskData {
time: TimeRange;
}

type FilteredTasks = Operation<TaskData>;

/**
* Filters out tasks that are not in the given interval
* @param tasks array of tasks to filter
* @param interval interval to filter by
* Filters valid tasks to be shown in the chart
* @param tasks list of tasks as passed to the component
* @param interval interval as passed to the component
*/
export const filterOutOfInterval = (tasks: TaskData[], interval: Interval): TaskData[] =>
tasks.filter((task) => {
const taskInterval = toInterval(task.time);
return !!interval.intersection(taskInterval);
export const filterTasks = (tasks: TaskData[], interval: Interval): FilteredTasks => {
if (!tasks || !tasks.length) {
return { items: [], errors: [{ entity: "task", level: "warn", message: "No data" }] };
}

const { end: intervalEnd, start: intervalStart } = interval;
if (!intervalEnd || !intervalStart) {
return { items: [], errors: [{ entity: "interval", level: "warn", message: "Incomplete" }] };
}

const errors: KonvaTimelineError[] = [];
const items = tasks.filter(({ id: taskId, time: { start: taskStart, end: taskEnd } }) => {
if (taskStart >= taskEnd) {
errors.push({ entity: "task", level: "error", message: "Invalid time", refId: taskId });
return false;
}

if (taskEnd < intervalStart.toMillis() || taskStart > intervalEnd.toMillis()) {
errors.push({ entity: "task", level: "warn", message: "Outside interval", refId: taskId });
return false;
}

return true;
});

return { items, errors };
};
7 changes: 4 additions & 3 deletions src/timeline/TimelineContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { createContext, PropsWithChildren, useContext, useMemo, useState
import { DateTime, Interval } from "luxon";

import { addHeaderResource } from "../resources/utils/resources";
import { filterOutOfInterval, TaskData } from "../tasks/utils/tasks";
import { filterTasks, TaskData } from "../tasks/utils/tasks";
import { DEFAULT_GRID_COLUMN_WIDTH, DEFAULT_GRID_ROW_HEIGHT, MINIMUM_GRID_ROW_HEIGHT } from "../utils/dimensions";
import { logDebug, logWarn } from "../utils/logger";
import { TimeRange, toInterval } from "../utils/time-range";
Expand Down Expand Up @@ -184,10 +184,11 @@ export const TimelineProvider = ({
DateTime.fromMillis(visibleTimeBlocks[visibleTimeBlocks.length - 1].start!.toMillis())
);

const ts = filterOutOfInterval(externalTasks, interval);
const { items } = filterTasks(externalTasks, interval);

const end = DateTime.now().toMillis();
logDebug("TimelineProvider", `Tasks preparation took ${end - start} ms`);
return ts;
return items;
}, [externalTasks, visibleTimeBlocks]);

const theme = useMemo((): TimelineTheme => {
Expand Down
15 changes: 15 additions & 0 deletions src/utils/operations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
type DataEntity = "interval" | "task";

type ErrorLevel = "error" | "warn";

export interface KonvaTimelineError {
entity: DataEntity;
level: ErrorLevel;
message: string;
refId?: string;
}

export type Operation<T> = {
items: T[];
errors: KonvaTimelineError[];
};

0 comments on commit 55791e8

Please sign in to comment.