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 { buildMarketCsv } from "./csv-exporter";
|
||||||
|
import { createBatchPayload, type BatchPayload } from "./batch-payload";
|
||||||
import {
|
import {
|
||||||
applyRowOrder,
|
applyRowOrder,
|
||||||
applyRowVisibility,
|
applyRowVisibility,
|
||||||
@ -16,6 +17,11 @@ import {
|
|||||||
setToolbarExportStatus
|
setToolbarExportStatus
|
||||||
} from "./plugin-toolbar";
|
} from "./plugin-toolbar";
|
||||||
import { createMarketResultStore } from "./result-store";
|
import { createMarketResultStore } from "./result-store";
|
||||||
|
import {
|
||||||
|
isAuthResponseMessage,
|
||||||
|
type AuthStateValue
|
||||||
|
} from "../../shared/auth-messages";
|
||||||
|
import { createBatchSubmitClient } from "../../shared/batch-submit-client";
|
||||||
import type {
|
import type {
|
||||||
MarketApiResult,
|
MarketApiResult,
|
||||||
MarketFilterState,
|
MarketFilterState,
|
||||||
@ -33,24 +39,38 @@ interface MutationObserverLike {
|
|||||||
export interface CreateMarketControllerOptions {
|
export interface CreateMarketControllerOptions {
|
||||||
buildCsv?: (records: MarketRecord[]) => string;
|
buildCsv?: (records: MarketRecord[]) => string;
|
||||||
document: Document;
|
document: Document;
|
||||||
|
getAuthState?: () => Promise<AuthStateValue>;
|
||||||
loadAuthorMetrics?: (authorId: string) => Promise<MarketApiResult>;
|
loadAuthorMetrics?: (authorId: string) => Promise<MarketApiResult>;
|
||||||
mutationObserverFactory?: (
|
mutationObserverFactory?: (
|
||||||
callback: MutationCallback
|
callback: MutationCallback
|
||||||
) => MutationObserverLike;
|
) => MutationObserverLike;
|
||||||
onCsvReady?: (csv: string) => void;
|
onCsvReady?: (csv: string) => void;
|
||||||
|
promptBatchName?: () => string | null;
|
||||||
resultStore?: ReturnType<typeof createMarketResultStore>;
|
resultStore?: ReturnType<typeof createMarketResultStore>;
|
||||||
|
submitBatch?: (payload: BatchPayload) => Promise<unknown>;
|
||||||
window: Window;
|
window: Window;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMarketController(options: CreateMarketControllerOptions) {
|
export function createMarketController(options: CreateMarketControllerOptions) {
|
||||||
const marketApiClient = createMarketApiClient();
|
const marketApiClient = createMarketApiClient();
|
||||||
|
const sendRuntimeMessage = createRuntimeMessageSender();
|
||||||
const resultStore = options.resultStore ?? createMarketResultStore();
|
const resultStore = options.resultStore ?? createMarketResultStore();
|
||||||
const loadAuthorMetrics =
|
const loadAuthorMetrics =
|
||||||
options.loadAuthorMetrics ?? marketApiClient.loadAuthorAseInfo;
|
options.loadAuthorMetrics ?? marketApiClient.loadAuthorAseInfo;
|
||||||
const buildCsv = options.buildCsv ?? buildMarketCsv;
|
const buildCsv = options.buildCsv ?? buildMarketCsv;
|
||||||
|
const getAuthState = options.getAuthState ?? (() => readAuthState(sendRuntimeMessage));
|
||||||
const mutationObserverFactory =
|
const mutationObserverFactory =
|
||||||
options.mutationObserverFactory ??
|
options.mutationObserverFactory ??
|
||||||
((callback: MutationCallback) => new MutationObserver(callback));
|
((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({
|
const exportRangeController = createExportRangeController({
|
||||||
document: options.document,
|
document: options.document,
|
||||||
onProgress: ({ currentPage, totalPages }) => {
|
onProgress: ({ currentPage, totalPages }) => {
|
||||||
@ -119,7 +139,46 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSubmitBatch: async () => {
|
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) {
|
if (target.mode === "count" && target.pageCount <= 1) {
|
||||||
setToolbarExportStatus(toolbar, "导出中...");
|
setToolbarExportStatus(toolbar, `${inProgressLabel}...`);
|
||||||
await prepareCurrentPageForExport();
|
await prepareCurrentPageForExport();
|
||||||
return getVisibleOrderedRecords();
|
return getVisibleOrderedRecords();
|
||||||
}
|
}
|
||||||
@ -556,6 +618,32 @@ function mergeFieldMap<T extends Record<string, string | undefined>>(
|
|||||||
return merged as T;
|
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(
|
function mergeStringValue(
|
||||||
current: string | undefined,
|
current: string | undefined,
|
||||||
incoming: string | undefined
|
incoming: string | undefined
|
||||||
|
|||||||
@ -837,6 +837,107 @@ describe("market-content-entry", () => {
|
|||||||
).toContain("有效页数");
|
).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 () => {
|
test("export only includes records that are present on the current page", async () => {
|
||||||
document.body.innerHTML = buildMarketFixture();
|
document.body.innerHTML = buildMarketFixture();
|
||||||
const resultStore = createMarketResultStore();
|
const resultStore = createMarketResultStore();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user