Machine Coding Problem

Task Planner (Jira-lite)

maco30maco60macoAllproductivitystate-patternobserver-patternfactory-methodcapacity-check
Commonly Asked By:AtlassianMicrosoftSlack

Functional Scope (In-Scope)

  • Backlog & Sprint Allocations: Support Project, Sprint, and Task hierarchical trees.
  • State Machine Transitions (State Pattern): Enforce valid task status migrations using distinct TaskState pattern entities.
  • Sprint Velocity warnings: Calculate sprint story point loads, firing warnings if capacity limits are breached.
  • Observer Updates: Enable subscribers to receive log updates upon task state transitions.
  • Polymorphic Task Types: Factory resolution for Feature and Bug tasks.

Explicit Boundaries (Out-of-Scope)

  • No Automatic Agile Release Planning AI: Bypasses sophisticated auto-balancing resources or burn-down predictions.
  • No Rich Document Comments editor: File system lacks complex collaborative text threads, file attachment uploads, or Markdown styling.

Clean reference designs detailing sprint capacities in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

enum TaskStatus { TODO, IN_PROGRESS, REVIEW, DONE }
enum TaskType { BUG, FEATURE, STORY }

interface TaskState {
    boolean transitionTo(Task task, TaskStatus nextStatus);
    TaskStatus getStatus();
}

class TodoState implements TaskState {
    @Override
    public boolean transitionTo(Task task, TaskStatus nextStatus) {
        if (nextStatus == TaskStatus.IN_PROGRESS) {
            task.setState(new InProgressState());
            return true;
        }
        return false;
    }
    @Override
    public TaskStatus getStatus() { return TaskStatus.TODO; }
}

class InProgressState implements TaskState {
    @Override
    public boolean transitionTo(Task task, TaskStatus nextStatus) {
        if (nextStatus == TaskStatus.REVIEW) {
            task.setState(new ReviewState());
            return true;
        } else if (nextStatus == TaskStatus.TODO) {
            task.setState(new TodoState());
            return true;
        }
        return false;
    }
    @Override
    public TaskStatus getStatus() { return TaskStatus.IN_PROGRESS; }
}

class ReviewState implements TaskState {
    @Override
    public boolean transitionTo(Task task, TaskStatus nextStatus) {
        if (nextStatus == TaskStatus.DONE) {
            task.setState(new DoneState());
            return true;
        } else if (nextStatus == TaskStatus.IN_PROGRESS) {
            task.setState(new InProgressState());
            return true;
        }
        return false;
    }
    @Override
    public TaskStatus getStatus() { return TaskStatus.REVIEW; }
}

class DoneState implements TaskState {
    @Override
    public boolean transitionTo(Task task, TaskStatus nextStatus) {
        return false; // Done is terminal
    }
    @Override
    public TaskStatus getStatus() { return TaskStatus.DONE; }
}

interface TaskObserver {
    void onTaskStatusChanged(String taskId, TaskStatus oldStatus, TaskStatus newStatus);
}

class ConsoleTaskLogger implements TaskObserver {
    @Override
    public void onTaskStatusChanged(String taskId, TaskStatus oldStatus, TaskStatus newStatus) {
        System.out.println(String.format("[Observer Log] Task: %s transitioned from %s to %s", taskId, oldStatus, newStatus));
    }
}

abstract class Task {
    private final String id;
    private final String title;
    private final int storyPoints;
    private TaskState state;
    private String assignee;
    private final List<TaskObserver> observers = new CopyOnWriteArrayList<>();
    private final ReentrantLock lock = new ReentrantLock();

    public Task(String id, String title, int storyPoints) {
        this.id = id;
        this.title = title;
        this.storyPoints = storyPoints;
        this.state = new TodoState();
    }

    public String getId() { return id; }
    public String getTitle() { return title; }
    public int getStoryPoints() { return storyPoints; }
    
    public TaskStatus getStatus() { 
        lock.lock();
        try { return state.getStatus(); } finally { lock.unlock(); }
    }
    
    public void setAssignee(String assignee) {
        lock.lock();
        try { this.assignee = assignee; } finally { lock.unlock(); }
    }
    
    public String getAssignee() {
        lock.lock();
        try { return assignee; } finally { lock.unlock(); }
    }

    public void setState(TaskState state) {
        this.state = state;
    }

    public void addObserver(TaskObserver observer) { observers.add(observer); }
    public void removeObserver(TaskObserver observer) { observers.remove(observer); }

    public boolean transitionTo(TaskStatus nextStatus) {
        lock.lock();
        try {
            TaskStatus oldStatus = state.getStatus();
            if (state.transitionTo(this, nextStatus)) {
                notifyObservers(oldStatus, nextStatus);
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    private void notifyObservers(TaskStatus oldStatus, TaskStatus newStatus) {
        for (TaskObserver obs : observers) {
            obs.onTaskStatusChanged(id, oldStatus, newStatus);
        }
    }
}

class BugTask extends Task {
    public BugTask(String id, String title, int storyPoints) { super(id, title, storyPoints); }
}

class FeatureTask extends Task {
    public FeatureTask(String id, String title, int storyPoints) { super(id, title, storyPoints); }
}

class TaskFactory {
    public static Task createTask(TaskType type, String id, String title, int storyPoints) {
        switch (type) {
            case BUG: return new BugTask(id, title, storyPoints);
            case FEATURE: return new FeatureTask(id, title, storyPoints);
            default: throw new IllegalArgumentException("Unsupported type: " + type);
        }
    }
}

class Sprint {
    private final String name;
    private final int capacity;
    private final List<Task> tasks = new CopyOnWriteArrayList<>();
    private final ReentrantLock lock = new ReentrantLock();

    public Sprint(String name, int capacity) {
        this.name = name;
        this.capacity = capacity;
    }

    public boolean addTask(Task task) {
        lock.lock();
        try {
            int currentSP = tasks.stream().mapToInt(Task::getStoryPoints).sum();
            if (currentSP + task.getStoryPoints() > capacity) {
                System.out.println(String.format("[Warning] Adding Task %s (Points: %d) breaches capacity in Sprint: %s (Max: %d)",
                        task.getId(), task.getStoryPoints(), name, capacity));
            }
            tasks.add(task);
            return true;
        } finally {
            lock.unlock();
        }
    }

    public List<Task> getTasks() { return Collections.unmodifiableList(tasks); }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== Task Planner Simulation ===");
        Sprint sprint = new Sprint("Sprint-01", 10);

        Task t1 = TaskFactory.createTask(TaskType.FEATURE, "JIRA-101", "Implement Google OAuth", 5);
        Task t2 = TaskFactory.createTask(TaskType.BUG, "JIRA-102", "Fix session timeout crash", 3);
        Task t3 = TaskFactory.createTask(TaskType.FEATURE, "JIRA-103", "Setup telemetry sinks", 5);

        TaskObserver logger = new ConsoleTaskLogger();
        t1.addObserver(logger);
        t2.addObserver(logger);

        sprint.addTask(t1);
        sprint.addTask(t2);
        sprint.addTask(t3); // Triggers capacity check log

        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.submit(() -> t1.transitionTo(TaskStatus.IN_PROGRESS));
        executor.submit(() -> t2.transitionTo(TaskStatus.IN_PROGRESS));

        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.SECONDS);
    }
}