155 lines
5.6 KiB
C
155 lines
5.6 KiB
C
|
|
#pragma once
|
||
|
|
|
||
|
|
#include "WorkflowState.h"
|
||
|
|
|
||
|
|
#include <algorithm>
|
||
|
|
#include <map>
|
||
|
|
#include <string>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
struct TaskBoardFilter {
|
||
|
|
std::string workerType;
|
||
|
|
std::string priority;
|
||
|
|
std::string language;
|
||
|
|
std::string fileContains;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct TaskCard {
|
||
|
|
std::string itemId;
|
||
|
|
std::string title;
|
||
|
|
std::string workerType;
|
||
|
|
std::string workerIcon;
|
||
|
|
std::string priority;
|
||
|
|
std::string language;
|
||
|
|
std::string file;
|
||
|
|
std::string status;
|
||
|
|
std::vector<std::string> tags;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct TaskBoardColumn {
|
||
|
|
std::string key;
|
||
|
|
std::string label;
|
||
|
|
std::vector<TaskCard> cards;
|
||
|
|
};
|
||
|
|
|
||
|
|
class WorkflowTaskBoard {
|
||
|
|
public:
|
||
|
|
static std::vector<TaskBoardColumn> build(const WorkflowState& wf,
|
||
|
|
const TaskBoardFilter& filter = {}) {
|
||
|
|
std::map<std::string, TaskBoardColumn> cols;
|
||
|
|
cols["pending"] = {"pending", "Pending", {}};
|
||
|
|
cols["ready"] = {"ready", "Ready", {}};
|
||
|
|
cols["in-progress"] = {"in-progress", "In Progress", {}};
|
||
|
|
cols["review"] = {"review", "Review", {}};
|
||
|
|
cols["complete"] = {"complete", "Complete", {}};
|
||
|
|
|
||
|
|
for (const auto& status : {WI_PENDING, WI_READY, WI_ASSIGNED, WI_IN_PROGRESS,
|
||
|
|
WI_REVIEW, WI_COMPLETE, WI_REJECTED}) {
|
||
|
|
for (const auto& wi : wf.queue.getByStatus(status)) {
|
||
|
|
TaskCard card = toCard(wi);
|
||
|
|
if (!matchesFilter(card, filter)) continue;
|
||
|
|
std::string col = normalizeColumnKey(wi.status);
|
||
|
|
if (cols.find(col) == cols.end()) continue;
|
||
|
|
cols[col].cards.push_back(card);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<TaskBoardColumn> out;
|
||
|
|
for (const auto& k : {"pending", "ready", "in-progress", "review", "complete"}) {
|
||
|
|
auto& c = cols[k];
|
||
|
|
std::sort(c.cards.begin(), c.cards.end(),
|
||
|
|
[](const TaskCard& a, const TaskCard& b) {
|
||
|
|
if (priorityToInt(a.priority) != priorityToInt(b.priority)) {
|
||
|
|
return priorityToInt(a.priority) < priorityToInt(b.priority);
|
||
|
|
}
|
||
|
|
return a.itemId < b.itemId;
|
||
|
|
});
|
||
|
|
out.push_back(c);
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool moveItem(WorkflowState& wf, const std::string& itemId,
|
||
|
|
const std::string& targetColumn) {
|
||
|
|
auto itemOpt = wf.queue.getItem(itemId);
|
||
|
|
if (!itemOpt.has_value()) return false;
|
||
|
|
std::string status = columnToStatus(targetColumn);
|
||
|
|
if (status.empty()) return false;
|
||
|
|
WorkItem item = *itemOpt;
|
||
|
|
item.status = status;
|
||
|
|
return wf.queue.updateItem(itemId, item);
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
static TaskCard toCard(const WorkItem& wi) {
|
||
|
|
TaskCard c;
|
||
|
|
c.itemId = wi.id;
|
||
|
|
c.title = wi.nodeName.empty() ? wi.nodeId : wi.nodeName;
|
||
|
|
c.workerType = wi.workerType;
|
||
|
|
c.workerIcon = iconForWorker(wi.workerType);
|
||
|
|
c.priority = wi.priority;
|
||
|
|
c.language = languageFromPath(wi.bufferId);
|
||
|
|
c.file = wi.bufferId;
|
||
|
|
c.status = normalizeColumnKey(wi.status);
|
||
|
|
c.tags = tagsForItem(wi);
|
||
|
|
return c;
|
||
|
|
}
|
||
|
|
|
||
|
|
static std::vector<std::string> tagsForItem(const WorkItem& wi) {
|
||
|
|
std::vector<std::string> tags;
|
||
|
|
if (!wi.contextWidth.empty()) tags.push_back("ctx:" + wi.contextWidth);
|
||
|
|
if (wi.reviewRequired) tags.push_back("review");
|
||
|
|
if (!wi.dependencies.empty()) tags.push_back("deps:" + std::to_string(wi.dependencies.size()));
|
||
|
|
return tags;
|
||
|
|
}
|
||
|
|
|
||
|
|
static std::string normalizeColumnKey(const std::string& status) {
|
||
|
|
if (status == WI_ASSIGNED || status == WI_IN_PROGRESS) return "in-progress";
|
||
|
|
if (status == WI_REJECTED) return "pending";
|
||
|
|
if (status == WI_PENDING) return "pending";
|
||
|
|
if (status == WI_READY) return "ready";
|
||
|
|
if (status == WI_REVIEW) return "review";
|
||
|
|
if (status == WI_COMPLETE) return "complete";
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
static std::string columnToStatus(const std::string& column) {
|
||
|
|
if (column == "pending") return WI_PENDING;
|
||
|
|
if (column == "ready") return WI_READY;
|
||
|
|
if (column == "in-progress") return WI_IN_PROGRESS;
|
||
|
|
if (column == "review") return WI_REVIEW;
|
||
|
|
if (column == "complete") return WI_COMPLETE;
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
static std::string iconForWorker(const std::string& worker) {
|
||
|
|
if (worker == "deterministic" || worker == "template") return "D";
|
||
|
|
if (worker == "slm") return "S";
|
||
|
|
if (worker == "llm") return "L";
|
||
|
|
if (worker == "human") return "H";
|
||
|
|
return "?";
|
||
|
|
}
|
||
|
|
|
||
|
|
static std::string languageFromPath(const std::string& path) {
|
||
|
|
auto dot = path.find_last_of('.');
|
||
|
|
if (dot == std::string::npos) return "";
|
||
|
|
std::string ext = path.substr(dot);
|
||
|
|
if (ext == ".cpp" || ext == ".cc" || ext == ".hpp" || ext == ".h") return "cpp";
|
||
|
|
if (ext == ".py") return "python";
|
||
|
|
if (ext == ".js") return "javascript";
|
||
|
|
if (ext == ".ts") return "typescript";
|
||
|
|
if (ext == ".java") return "java";
|
||
|
|
if (ext == ".rs") return "rust";
|
||
|
|
if (ext == ".go") return "go";
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool matchesFilter(const TaskCard& card, const TaskBoardFilter& f) {
|
||
|
|
if (!f.workerType.empty() && card.workerType != f.workerType) return false;
|
||
|
|
if (!f.priority.empty() && card.priority != f.priority) return false;
|
||
|
|
if (!f.language.empty() && card.language != f.language) return false;
|
||
|
|
if (!f.fileContains.empty() && card.file.find(f.fileContains) == std::string::npos) return false;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
};
|