Develop #41
@@ -28,6 +28,7 @@ from backend.dependencies import (
|
|||||||
database_session,
|
database_session,
|
||||||
request_context,
|
request_context,
|
||||||
)
|
)
|
||||||
|
from backend.scripts import _escape_like_pattern
|
||||||
from backend.schemas import (
|
from backend.schemas import (
|
||||||
CompleteResourceUploadRequest,
|
CompleteResourceUploadRequest,
|
||||||
CreateResourceUploadRequest,
|
CreateResourceUploadRequest,
|
||||||
@@ -402,7 +403,6 @@ async def list_resources(
|
|||||||
if keyword:
|
if keyword:
|
||||||
# Escape LIKE metacharacters so a search like "100%" or "my_file"
|
# Escape LIKE metacharacters so a search like "100%" or "my_file"
|
||||||
# doesn't act as a wildcard. The outer "%...%" wildcards stay raw.
|
# doesn't act as a wildcard. The outer "%...%" wildcards stay raw.
|
||||||
from backend.scripts import _escape_like_pattern # local import: avoid cycle
|
|
||||||
escaped = _escape_like_pattern(keyword.strip())
|
escaped = _escape_like_pattern(keyword.strip())
|
||||||
statement = statement.where(
|
statement = statement.where(
|
||||||
DataResources.resource_name.like(f"%{escaped}%", escape="\\")
|
DataResources.resource_name.like(f"%{escaped}%", escape="\\")
|
||||||
|
|||||||
@@ -1138,11 +1138,18 @@ async def list_scripts(
|
|||||||
# 避免被 listScripts 的懒加载语义污染。该路由必须在 /scripts/{script_id} 之前声明
|
# 避免被 listScripts 的懒加载语义污染。该路由必须在 /scripts/{script_id} 之前声明
|
||||||
# ——FastAPI 按声明顺序匹配,否则 `count` 会被当作 script_id 命中 get_script。
|
# ——FastAPI 按声明顺序匹配,否则 `count` 会被当作 script_id 命中 get_script。
|
||||||
#
|
#
|
||||||
# 范围与 list_scripts(parent_path="") 对齐:INNER JOIN 到 StorageObjects 以排除
|
# Scope: equals the UNION of list_scripts across every parent_path
|
||||||
# 孤儿脚本(其 current_object_id 没有 joinable row),按用户子树
|
# within the user's subtree, NOT just the root-level call. We
|
||||||
# (workspace/{user_id}/) 过滤。否则:
|
# intentionally include deeper descendants because the dashboard's
|
||||||
# - 含孤儿 → 数字虚高
|
# "全部脚本" / "工作副本" counts reflect the whole workspace, not
|
||||||
# - workspace 范围 → 多成员工作区里 dashboard 会显示用户看不见的脚本
|
# only the top level.
|
||||||
|
#
|
||||||
|
# Implementation choices and why:
|
||||||
|
# - INNER JOIN to StorageObjects so orphans (current_object_id has no
|
||||||
|
# joinable row) are excluded.
|
||||||
|
# - User subtree filter so multi-member workspaces don't show counts
|
||||||
|
# the requester can't see.
|
||||||
|
# - No NOT-LIKE filter because the count wants descendants too.
|
||||||
@router.get("/api/v1/scripts/count")
|
@router.get("/api/v1/scripts/count")
|
||||||
async def count_scripts(
|
async def count_scripts(
|
||||||
context: RequestContext = Depends(request_context),
|
context: RequestContext = Depends(request_context),
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ let _api: WorkspaceBoundApi | null = null;
|
|||||||
let _previewController: AbortController | null = null;
|
let _previewController: AbortController | null = null;
|
||||||
let _previewRequest = 0;
|
let _previewRequest = 0;
|
||||||
let _pythonEditorOpeningIds = new Set<string>();
|
let _pythonEditorOpeningIds = new Set<string>();
|
||||||
|
let _scriptCountSeq = 0;
|
||||||
|
|
||||||
export const bindScriptWorkspaceApi = (api: WorkspaceBoundApi | null) => {
|
export const bindScriptWorkspaceApi = (api: WorkspaceBoundApi | null) => {
|
||||||
_api = api;
|
_api = api;
|
||||||
@@ -397,17 +398,25 @@ export const useScriptWorkspaceStore = create<State>((set, get) => {
|
|||||||
|
|
||||||
loadScriptCount: async () => {
|
loadScriptCount: async () => {
|
||||||
const api = requireApi();
|
const api = requireApi();
|
||||||
if (get().scriptCountLoading) return;
|
// Always fire — don't dedupe via the loading flag. Rapid workspace
|
||||||
|
// switches would otherwise drop the new fetch and leave the
|
||||||
|
// dashboard showing the previous workspace's count. The sequence
|
||||||
|
// counter below discards stale responses instead.
|
||||||
|
const seq = ++_scriptCountSeq;
|
||||||
set({ scriptCountLoading: true, scriptCount: null });
|
set({ scriptCountLoading: true, scriptCount: null });
|
||||||
try {
|
try {
|
||||||
const total = await api.countScripts();
|
const total = await api.countScripts();
|
||||||
|
if (_scriptCountSeq !== seq) return; // a newer fetch superseded us
|
||||||
set({ scriptCount: total });
|
set({ scriptCount: total });
|
||||||
} catch {
|
} catch {
|
||||||
|
if (_scriptCountSeq !== seq) return;
|
||||||
// Leave previous value in place; the dashboard already tolerates
|
// Leave previous value in place; the dashboard already tolerates
|
||||||
// a stale count by rendering `scriptCount ?? 0`. Don't toast —
|
// a stale count by rendering `scriptCount ?? 0`. Don't toast —
|
||||||
// the dashboard's other metrics are best-effort.
|
// the dashboard's other metrics are best-effort.
|
||||||
} finally {
|
} finally {
|
||||||
set({ scriptCountLoading: false });
|
if (_scriptCountSeq === seq) {
|
||||||
|
set({ scriptCountLoading: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user