feat: 移除計時並停用報表預覽

This commit is contained in:
2026-09-21 17:41:44 +08:00
parent 5a259a95af
commit 7f723adff8
34 changed files with 100 additions and 6113 deletions
+1 -92
View File
@@ -30,7 +30,7 @@ import { fileURLToPath } from 'node:url';
/** 帶錯誤碼的失敗。呼叫端靠 code 分辨是哪一步壞了,訊息則要能指出去哪裡改。 */
export class ScriptError extends Error {
/**
* @param {string} code 可區分的錯誤碼,例如 TIME_TRACKER_OFF
* @param {string} code 可區分的錯誤碼,例如 REPORT_UNAVAILABLE
* @param {string} message 給人看的訊息,必要時附上「該改哪裡」
*/
constructor(code, message) {
@@ -776,7 +776,6 @@ export async function preflight(login, repo) {
const user = await checkLogin(login);
const info = await fetchRepo(login, repo);
await checkIssueWrite(login, repo, info);
checkTimeTracker(info);
return { repo: info, user };
}
@@ -896,15 +895,6 @@ async function checkIssueWrite(login, repo, info) {
}
}
/** 第四層:repo 是否已開啟時間追蹤 */
function checkTimeTracker(info) {
if (info.internal_tracker?.enable_time_tracker !== true) {
throw new ScriptError(
'TIME_TRACKER_OFF',
'repo 尚未開啟時間追蹤,工時碼錶無法運作;請到 Settings → Advanced Settings → Enable Time Tracker 開啟',
);
}
}
/**
* 讀一個由 flag 指定的文字檔。
@@ -1013,87 +1003,6 @@ export async function mergedByMe(login, repo, id, me) {
return reactions.some((reaction) => reaction.content === '+1' && reaction.user?.login === me);
}
// ── 碼錶 ───────────────────────────────────────────────────────────
/**
* 目前跑在自己身上的碼錶。
*
* Gitea 只讓人讀自己的碼錶,看不到別人的——所以這份清單的語意永遠是「**我**的錶」,
* 它用來發現自己忘了停上一顆,不是用來判斷別人有沒有在做(那看 assignee)。
* @param {{base: string, token: string}} login
* @returns {Promise<object[]>}
*/
export async function listStopwatches(login) {
const path = '/user/stopwatches';
return expectOk(await giteaRequest(login, 'GET', path), `GET ${path}`) ?? [];
}
/**
* 「你的錶正跑在別顆議題上」的擋路錯誤。領取與起錶都會撞到它,訊息只寫一份。
*
* 一定要明說停錶不會動到工作樹:使用者常以為停錶等於放棄那顆工作包,於是寧可不停,
* 而工時就記到別顆議題去了。碼錶只管時間,工作樹只管檔案,兩者互不相干。
* @param {object} watch listStopwatches 裡的一顆錶
* @param {string} 動作 擋在哪件事之前,例如「領取」「起錶」
*/
export function stopwatchElsewhere(watch, 動作) {
return new ScriptError(
'STOPWATCH_ON_OTHER_ISSUE',
`你的碼錶正跑在 ${watch.repo_owner_name}/${watch.repo_name} 的議題 ` +
`#${watch.issue_index} 上,${動作}前請先手動停錶,否則工時會記到那一顆去;` +
'停錶只停計時,不會動到任何既有的工作樹',
);
}
/**
* 這些碼錶裡,跑在指定議題上的那一顆。
* 比對要連 repo 一起看:不同 repo 的同號議題是兩件事。
* @param {object[]} watches listStopwatches 的結果
* @param {string} repo owner/name
* @param {number} index
* @returns {object|null}
*/
export function stopwatchOnIssue(watches, repo, index) {
return (
watches.find(
(watch) => `${watch.repo_owner_name}/${watch.repo_name}` === repo && watch.issue_index === index,
) ?? null
);
}
/**
* 「這顆議題上沒有碼錶在跑」的回法不只一種:看過 500,也看過 409。
* 狀態碼隨站台版本而異,所以認的是「狀態碼在這一組裡 **且** 訊息說的是碼錶」——
* 只看訊息會把真的伺服器錯誤一起吞掉,只看狀態碼會把別的衝突也當成沒錶。
*/
const NO_STOPWATCH_STATUS = [409, 500];
/**
* 停錶。停在指定議題上,只停那一顆——端點本身就是議題範圍的,
* 停錶不會波及別顆議題上的錶,那正是領取鎖那條規則要守住的事。
*
* 錶沒在跑不算失敗:停錶多半排在別的事情做完之後(開完 PR、回報完),
* 把「本來就沒在跑」報成失敗,只會讓人以為前面那件事沒做成而重跑一次。
*
* @param {{base: string, token: string}} login
* @param {string} repo owner/name
* @param {number} index
* @returns {Promise<boolean>} 這次真的停了一支錶才是 true
*/
export async function stopStopwatch(login, repo, index) {
const path = `/repos/${repo}/issues/${index}/stopwatch/stop`;
const response = await giteaRequest(login, 'POST', path, { body: {} });
if (response.status >= 200 && response.status < 300) return true;
if (
NO_STOPWATCH_STATUS.includes(response.status) &&
/stopwatch/i.test(response.body?.message ?? '')
) {
return false;
}
expectOk(response, `POST ${path}`);
return false;
}
// ── 標籤 ───────────────────────────────────────────────────────────