feat: wire market batch submission flow
This commit is contained in:
parent
3c672f8355
commit
766c6a624f
@ -1,4 +1,5 @@
|
||||
import { buildMarketCsv } from "./csv-exporter";
|
||||
import { createBatchPayload, type BatchPayload } from "./batch-payload";
|
||||
import {
|
||||
applyRowOrder,
|
||||
applyRowVisibility,
|
||||
@ -16,6 +17,11 @@ import {
|
||||
setToolbarExportStatus
|
||||
} from "./plugin-toolbar";
|
||||
import { createMarketResultStore } from "./result-store";
|
||||
import {
|
||||
isAuthResponseMessage,
|
||||
type AuthStateValue
|
||||
} from "../../shared/auth-messages";
|
||||
import { createBatchSubmitClient } from "../../shared/batch-submit-client";
|
||||
import type {
|
||||
MarketApiResult,
|
||||
MarketFilterState,
|
||||
@ -33,24 +39,38 @@ interface MutationObserverLike {
|
||||
export interface CreateMarketControllerOptions {
|
||||
buildCsv?: (records: MarketRecord[]) => string;
|
||||
document: Document;
|
||||
getAuthState?: () => Promise<AuthStateValue>;
|
||||
loadAuthorMetrics?: (authorId: string) => Promise<MarketApiResult>;
|
||||
mutationObserverFactory?: (
|
||||
callback: MutationCallback
|
||||
) => MutationObserverLike;
|
||||
onCsvReady?: (csv: string) => void;
|
||||
promptBatchName?: () => string | null;
|
||||
resultStore?: ReturnType<typeof createMarketResultStore>;
|
||||
submitBatch?: (payload: BatchPayload) => Promise<unknown>;
|
||||
window: Window;
|
||||
}
|
||||
|
||||
export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
const marketApiClient = createMarketApiClient();
|
||||
const sendRuntimeMessage = createRuntimeMessageSender();
|
||||
const resultStore = options.resultStore ?? createMarketResultStore();
|
||||
const loadAuthorMetrics =
|
||||
options.loadAuthorMetrics ?? marketApiClient.loadAuthorAseInfo;
|
||||
const buildCsv = options.buildCsv ?? buildMarketCsv;
|
||||
const getAuthState = options.getAuthState ?? (() => readAuthState(sendRuntimeMessage));
|
||||
const mutationObserverFactory =
|
||||
options.mutationObserverFactory ??
|
||||
((callback: MutationCallback) => new MutationObserver(callback));
|
||||
const promptBatchName =
|
||||
options.promptBatchName ??
|
||||
(() => options.window.prompt("请输入批次名称"));
|
||||
const submitBatch =
|
||||
options.submitBatch ??
|
||||
createBatchSubmitClient({
|
||||
baseUrl: "http://127.0.0.1:4319",
|
||||
sendMessage: sendRuntimeMessage
|
||||
}).submitBatch;
|
||||
const exportRangeController = createExportRangeController({
|
||||
document: options.document,
|
||||
onProgress: ({ currentPage, totalPages }) => {
|
||||
@ -119,7 +139,46 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
}
|
||||
},
|
||||
onSubmitBatch: async () => {
|
||||
setToolbarExportStatus(toolbar, "批次提交功能开发中");
|
||||
const exportTarget = readToolbarExportTarget(toolbar);
|
||||
if (!exportTarget.target) {
|
||||
setToolbarExportStatus(toolbar, exportTarget.error ?? "导出配置无效");
|
||||
return;
|
||||
}
|
||||
|
||||
const batchName = promptBatchName();
|
||||
if (batchName === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!batchName.trim()) {
|
||||
setToolbarExportStatus(toolbar, "请输入批次名称");
|
||||
return;
|
||||
}
|
||||
|
||||
setToolbarBusyState(toolbar, true);
|
||||
try {
|
||||
const records = await exportRecords(exportTarget.target, "提交中");
|
||||
const authState = await getAuthState();
|
||||
if (!authState.isAuthenticated) {
|
||||
throw new Error("请先登录插件");
|
||||
}
|
||||
|
||||
const payload = createBatchPayload({
|
||||
authState,
|
||||
batchName,
|
||||
createdAt: new Date().toISOString(),
|
||||
records
|
||||
});
|
||||
await submitBatch(payload);
|
||||
setToolbarExportStatus(toolbar, "批次提交成功");
|
||||
} catch (error) {
|
||||
setToolbarExportStatus(
|
||||
toolbar,
|
||||
error instanceof Error ? error.message : "批次提交失败,请稍后重试"
|
||||
);
|
||||
} finally {
|
||||
setToolbarBusyState(toolbar, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -228,9 +287,12 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
});
|
||||
}
|
||||
|
||||
async function exportRecords(target: MarketExportTarget): Promise<MarketRecord[]> {
|
||||
async function exportRecords(
|
||||
target: MarketExportTarget,
|
||||
inProgressLabel = "导出中"
|
||||
): Promise<MarketRecord[]> {
|
||||
if (target.mode === "count" && target.pageCount <= 1) {
|
||||
setToolbarExportStatus(toolbar, "导出中...");
|
||||
setToolbarExportStatus(toolbar, `${inProgressLabel}...`);
|
||||
await prepareCurrentPageForExport();
|
||||
return getVisibleOrderedRecords();
|
||||
}
|
||||
@ -556,6 +618,32 @@ function mergeFieldMap<T extends Record<string, string | undefined>>(
|
||||
return merged as T;
|
||||
}
|
||||
|
||||
function createRuntimeMessageSender(): (message: unknown) => Promise<unknown> {
|
||||
return (message: unknown) =>
|
||||
Promise.resolve(
|
||||
(
|
||||
globalThis as typeof globalThis & {
|
||||
chrome?: {
|
||||
runtime?: {
|
||||
sendMessage?: (payload: unknown) => Promise<unknown>;
|
||||
};
|
||||
};
|
||||
}
|
||||
).chrome?.runtime?.sendMessage?.(message)
|
||||
);
|
||||
}
|
||||
|
||||
async function readAuthState(
|
||||
sendMessage: (message: unknown) => Promise<unknown>
|
||||
): Promise<AuthStateValue> {
|
||||
const response = await sendMessage({ type: "auth:get-state" });
|
||||
if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:state") {
|
||||
throw new Error("请先登录插件");
|
||||
}
|
||||
|
||||
return response.value;
|
||||
}
|
||||
|
||||
function mergeStringValue(
|
||||
current: string | undefined,
|
||||
incoming: string | undefined
|
||||
|
||||
@ -837,6 +837,107 @@ describe("market-content-entry", () => {
|
||||
).toContain("有效页数");
|
||||
});
|
||||
|
||||
test("prompts for a batch name before submitting the current range", async () => {
|
||||
document.body.innerHTML = buildMarketFixture();
|
||||
const promptBatchName = vi.fn(() => "618达人筛选第一批");
|
||||
const submitBatch = vi.fn(async () => ({ ok: true }));
|
||||
|
||||
const { createMarketController } = await import("../src/content/market/index");
|
||||
const controller = trackController(createMarketController({
|
||||
document,
|
||||
getAuthState: async () => ({
|
||||
isAuthenticated: true,
|
||||
resource: "https://talent-search.intelligrow.cn",
|
||||
userInfo: { name: "王少卿", sub: "p7pdhhtde8kj" }
|
||||
}),
|
||||
loadAuthorMetrics: async () => ({
|
||||
success: false,
|
||||
reason: "request-failed"
|
||||
}),
|
||||
promptBatchName,
|
||||
submitBatch,
|
||||
window
|
||||
}));
|
||||
|
||||
await controller.ready;
|
||||
setSelectValue('[data-plugin-export-range="select"]', "current");
|
||||
dispatchChange('[data-plugin-export-range="select"]');
|
||||
|
||||
click('[data-plugin-batch-submit="button"]');
|
||||
await waitForMockCall(submitBatch, 40, 50);
|
||||
|
||||
expect(promptBatchName).toHaveBeenCalledTimes(1);
|
||||
expect(submitBatch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
batchId: expect.stringContaining("618达人筛选第一批-"),
|
||||
batchName: "618达人筛选第一批",
|
||||
logtoUserId: "p7pdhhtde8kj"
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("shows an error when the batch name is blank", async () => {
|
||||
document.body.innerHTML = buildMarketFixture();
|
||||
const promptBatchName = vi.fn(() => " ");
|
||||
const submitBatch = vi.fn(async () => ({ ok: true }));
|
||||
|
||||
const { createMarketController } = await import("../src/content/market/index");
|
||||
const controller = trackController(createMarketController({
|
||||
document,
|
||||
getAuthState: async () => ({
|
||||
isAuthenticated: true,
|
||||
resource: "https://talent-search.intelligrow.cn",
|
||||
userInfo: { name: "王少卿", sub: "p7pdhhtde8kj" }
|
||||
}),
|
||||
loadAuthorMetrics: async () => ({
|
||||
success: false,
|
||||
reason: "request-failed"
|
||||
}),
|
||||
promptBatchName,
|
||||
submitBatch,
|
||||
window
|
||||
}));
|
||||
|
||||
await controller.ready;
|
||||
click('[data-plugin-batch-submit="button"]');
|
||||
await flush();
|
||||
|
||||
expect(submitBatch).not.toHaveBeenCalled();
|
||||
expect(
|
||||
document.querySelector('[data-plugin-export-status="text"]')?.textContent
|
||||
).toContain("请输入批次名称");
|
||||
});
|
||||
|
||||
test("does nothing when the prompt is cancelled", async () => {
|
||||
document.body.innerHTML = buildMarketFixture();
|
||||
const promptBatchName = vi.fn(() => null);
|
||||
const submitBatch = vi.fn(async () => ({ ok: true }));
|
||||
|
||||
const { createMarketController } = await import("../src/content/market/index");
|
||||
const controller = trackController(createMarketController({
|
||||
document,
|
||||
getAuthState: async () => ({
|
||||
isAuthenticated: true,
|
||||
resource: "https://talent-search.intelligrow.cn",
|
||||
userInfo: { name: "王少卿", sub: "p7pdhhtde8kj" }
|
||||
}),
|
||||
loadAuthorMetrics: async () => ({
|
||||
success: false,
|
||||
reason: "request-failed"
|
||||
}),
|
||||
promptBatchName,
|
||||
submitBatch,
|
||||
window
|
||||
}));
|
||||
|
||||
await controller.ready;
|
||||
click('[data-plugin-batch-submit="button"]');
|
||||
await flush();
|
||||
|
||||
expect(promptBatchName).toHaveBeenCalledTimes(1);
|
||||
expect(submitBatch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("export only includes records that are present on the current page", async () => {
|
||||
document.body.innerHTML = buildMarketFixture();
|
||||
const resultStore = createMarketResultStore();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user