feat: add logto auth and backend metrics integration
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
export function renderMarketAuthGate(
|
||||
document: Document,
|
||||
currentWindow: Window
|
||||
): HTMLElement {
|
||||
const existingGate = document.querySelector(
|
||||
'[data-market-auth-gate="root"]'
|
||||
) as HTMLElement | null;
|
||||
|
||||
if (existingGate) {
|
||||
return existingGate;
|
||||
}
|
||||
|
||||
const root = document.createElement("section");
|
||||
root.dataset.marketAuthGate = "root";
|
||||
root.innerHTML = `
|
||||
<strong>请先登录插件</strong>
|
||||
<p>打开扩展弹窗完成登录后刷新本页</p>
|
||||
<button type="button" data-market-auth-help="button">去登录</button>
|
||||
`;
|
||||
|
||||
root
|
||||
.querySelector('[data-market-auth-help="button"]')
|
||||
?.addEventListener("click", () => {
|
||||
currentWindow.alert("请点击浏览器工具栏中的扩展图标完成登录");
|
||||
});
|
||||
|
||||
document.body.prepend(root);
|
||||
return root;
|
||||
}
|
||||
@@ -2,14 +2,17 @@ import {
|
||||
normalizeFractionRateDisplay,
|
||||
normalizeRateDisplay
|
||||
} from "../../shared/rate-normalizer";
|
||||
import type { AfterSearchRates } from "./types";
|
||||
import type { AfterSearchRates, BackendMetrics } from "./types";
|
||||
import type { MarketRecord } from "./types";
|
||||
|
||||
const BACKEND_COLUMN_KEY = "backendMetrics";
|
||||
const SINGLE_COLUMN_KEY = "singleVideoAfterSearchRate";
|
||||
const PERSONAL_COLUMN_KEY = "personalVideoAfterSearchRate";
|
||||
const ACTION_HEADER_TEXT = "操作";
|
||||
const AUTHOR_HEADER_TEXT = "达人信息";
|
||||
const BACKEND_HEADER_TEXT = "秒探指标";
|
||||
const UNAVAILABLE_RATE_TEXT = "暂无来源";
|
||||
const UNAVAILABLE_BACKEND_METRICS_TEXT = "暂无数据";
|
||||
const SERIALIZED_MARKET_ROWS_ATTRIBUTE = "data-sces-market-rows";
|
||||
|
||||
type RowOrderTarget = {
|
||||
@@ -20,6 +23,7 @@ type RowOrderTarget = {
|
||||
export interface MarketRowDom {
|
||||
authorId: string;
|
||||
authorName: string;
|
||||
backendMetricsCell: HTMLElement;
|
||||
exportFields?: Record<string, string>;
|
||||
hasDirectRatesSource?: boolean;
|
||||
personalCell: HTMLElement;
|
||||
@@ -105,6 +109,8 @@ export function renderMarketRowState(
|
||||
rowDom: MarketRowDom,
|
||||
record: MarketRecord
|
||||
): void {
|
||||
renderBackendMetricsCell(rowDom.backendMetricsCell, record);
|
||||
|
||||
if (record.status === "success" && record.rates) {
|
||||
rowDom.singleCell.textContent = readRateCellText(
|
||||
record.rates.singleVideoAfterSearchRate
|
||||
@@ -171,6 +177,7 @@ function syncSyntheticMarketTable(root: ParentNode): MarketTableDom | null {
|
||||
|
||||
ensureSyntheticHeaderCell(header, SINGLE_COLUMN_KEY, "单视频看后搜率");
|
||||
ensureSyntheticHeaderCell(header, PERSONAL_COLUMN_KEY, "个人视频看后搜率");
|
||||
ensureSyntheticHeaderCell(header, BACKEND_COLUMN_KEY, BACKEND_HEADER_TEXT);
|
||||
|
||||
const headerLabelsByField = readSyntheticHeaderLabels(header);
|
||||
const rows = Array.from(body.querySelectorAll("[data-market-row]")).map(
|
||||
@@ -178,12 +185,14 @@ function syncSyntheticMarketTable(root: ParentNode): MarketTableDom | null {
|
||||
const row = rowElement as HTMLElement;
|
||||
const singleCell = ensureSyntheticRowCell(row, SINGLE_COLUMN_KEY);
|
||||
const personalCell = ensureSyntheticRowCell(row, PERSONAL_COLUMN_KEY);
|
||||
const backendMetricsCell = ensureSyntheticRowCell(row, BACKEND_COLUMN_KEY);
|
||||
|
||||
return {
|
||||
authorId: row.dataset.authorId ?? "",
|
||||
authorName:
|
||||
row.querySelector('[data-market-field="authorName"]')?.textContent?.trim() ??
|
||||
"",
|
||||
backendMetricsCell,
|
||||
exportFields: readSyntheticExportFields(row, headerLabelsByField),
|
||||
hasDirectRatesSource: false,
|
||||
orderTargets: [
|
||||
@@ -274,6 +283,7 @@ function syncDivGridRoot(root: HTMLElement): MarketTableDom | null {
|
||||
const rowCount = getDirectContentCells(authorColumn).length;
|
||||
ensureDivHeaderCell(actionHeader, SINGLE_COLUMN_KEY, "单视频看后搜率");
|
||||
ensureDivHeaderCell(actionHeader, PERSONAL_COLUMN_KEY, "个人视频看后搜率");
|
||||
ensureDivHeaderCell(actionHeader, BACKEND_COLUMN_KEY, BACKEND_HEADER_TEXT);
|
||||
|
||||
const singleColumn = ensureDivBodyColumn(
|
||||
rightSection,
|
||||
@@ -287,6 +297,14 @@ function syncDivGridRoot(root: HTMLElement): MarketTableDom | null {
|
||||
PERSONAL_COLUMN_KEY,
|
||||
rowCount
|
||||
);
|
||||
const backendMetricsColumn = ensureDivBodyColumn(
|
||||
rightSection,
|
||||
actionColumn,
|
||||
BACKEND_COLUMN_KEY,
|
||||
rowCount
|
||||
);
|
||||
syncContainerWidth(actionHeader.parentElement);
|
||||
syncContainerWidth(rightSection);
|
||||
|
||||
const allBodyColumns = Array.from(bodySection.children).flatMap((section) =>
|
||||
section instanceof root.ownerDocument.defaultView!.HTMLElement
|
||||
@@ -301,6 +319,7 @@ function syncDivGridRoot(root: HTMLElement): MarketTableDom | null {
|
||||
const authorCells = getDirectContentCells(authorColumn);
|
||||
const singleCells = getDirectContentCells(singleColumn);
|
||||
const personalCells = getDirectContentCells(personalColumn);
|
||||
const backendMetricsCells = getDirectContentCells(backendMetricsColumn);
|
||||
const priceColumn = findPreviousColumn(actionColumn);
|
||||
const priceCells = priceColumn ? getDirectContentCells(priceColumn) : [];
|
||||
const vueMarketRows = readVueMarketRows(root);
|
||||
@@ -309,7 +328,8 @@ function syncDivGridRoot(root: HTMLElement): MarketTableDom | null {
|
||||
const rows = authorCells.flatMap((authorCell, index) => {
|
||||
const singleCell = singleCells[index] ?? null;
|
||||
const personalCell = personalCells[index] ?? null;
|
||||
if (!singleCell || !personalCell) {
|
||||
const backendMetricsCell = backendMetricsCells[index] ?? null;
|
||||
if (!singleCell || !personalCell || !backendMetricsCell) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -333,6 +353,7 @@ function syncDivGridRoot(root: HTMLElement): MarketTableDom | null {
|
||||
{
|
||||
authorId,
|
||||
authorName,
|
||||
backendMetricsCell,
|
||||
exportFields: readExportFieldsForDivGridRow(allHeaderCells, rowCells),
|
||||
hasDirectRatesSource:
|
||||
vueMarketRow?.hasDirectRatesSource ??
|
||||
@@ -395,7 +416,7 @@ function ensureSyntheticRowCell(row: HTMLElement, field: string): HTMLElement {
|
||||
return existingCell;
|
||||
}
|
||||
|
||||
const nextCell = row.ownerDocument.createElement("span");
|
||||
const nextCell = row.ownerDocument.createElement(field === BACKEND_COLUMN_KEY ? "div" : "span");
|
||||
nextCell.dataset.marketRowCell = field;
|
||||
row.appendChild(nextCell);
|
||||
return nextCell;
|
||||
@@ -423,6 +444,7 @@ function ensureDivHeaderCell(
|
||||
const nextCell = cloneElementShallow(referenceCell);
|
||||
nextCell.dataset.marketHeaderCell = field;
|
||||
nextCell.textContent = label;
|
||||
applyColumnWidth(nextCell, field);
|
||||
container.insertBefore(nextCell, actionHeader);
|
||||
return nextCell;
|
||||
}
|
||||
@@ -449,6 +471,7 @@ function ensureDivBodyColumn(
|
||||
const referenceColumn = findPreviousColumn(actionColumn) ?? actionColumn;
|
||||
const nextColumn = cloneElementShallow(referenceColumn);
|
||||
nextColumn.dataset.marketColumnGroup = field;
|
||||
applyColumnWidth(nextColumn, field);
|
||||
syncDivColumnCells(nextColumn, actionColumn, field, rowCount);
|
||||
container.insertBefore(nextColumn, actionColumn);
|
||||
return nextColumn;
|
||||
@@ -478,6 +501,7 @@ function syncDivColumnCells(
|
||||
? cloneElementShallow(templateCell)
|
||||
: createBareContentCell(column.ownerDocument);
|
||||
nextCell.dataset.marketRowCell = field;
|
||||
applyColumnWidth(nextCell, field);
|
||||
nextCell.textContent = "";
|
||||
column.appendChild(nextCell);
|
||||
}
|
||||
@@ -809,8 +833,9 @@ function normalizeExportCellText(value: string | null | undefined): string {
|
||||
|
||||
function shouldExportColumn(label: string): boolean {
|
||||
return Boolean(
|
||||
label &&
|
||||
label &&
|
||||
label !== ACTION_HEADER_TEXT &&
|
||||
label !== BACKEND_HEADER_TEXT &&
|
||||
label !== "单视频看后搜率" &&
|
||||
label !== "个人视频看后搜率"
|
||||
);
|
||||
@@ -819,3 +844,99 @@ function shouldExportColumn(label: string): boolean {
|
||||
function readRateCellText(value: string | undefined): string {
|
||||
return value ? normalizeRateDisplay(value) : UNAVAILABLE_RATE_TEXT;
|
||||
}
|
||||
|
||||
function applyColumnWidth(element: HTMLElement, field: string): void {
|
||||
if (field !== BACKEND_COLUMN_KEY) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.style.minWidth = "240px";
|
||||
element.style.width = "240px";
|
||||
}
|
||||
|
||||
function syncContainerWidth(container: Element | null): void {
|
||||
if (!(container instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const directChildren = Array.from(container.children).filter(
|
||||
(child): child is HTMLElement => child instanceof HTMLElement
|
||||
);
|
||||
const totalWidth = directChildren.reduce((sum, child) => {
|
||||
return sum + readElementWidth(child);
|
||||
}, 0);
|
||||
|
||||
if (totalWidth <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.style.width = `${totalWidth}px`;
|
||||
container.style.minWidth = `${totalWidth}px`;
|
||||
}
|
||||
|
||||
function readElementWidth(element: HTMLElement): number {
|
||||
const styleWidth = Number.parseFloat(element.style.width || "");
|
||||
if (Number.isFinite(styleWidth) && styleWidth > 0) {
|
||||
return styleWidth;
|
||||
}
|
||||
|
||||
const minWidth = Number.parseFloat(element.style.minWidth || "");
|
||||
if (Number.isFinite(minWidth) && minWidth > 0) {
|
||||
return minWidth;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function renderBackendMetricsCell(
|
||||
cell: HTMLElement,
|
||||
record: MarketRecord
|
||||
): void {
|
||||
if (
|
||||
record.backendMetricsStatus === "loading" ||
|
||||
(record.status === "loading" && !record.backendMetricsStatus)
|
||||
) {
|
||||
cell.textContent = "加载中...";
|
||||
return;
|
||||
}
|
||||
|
||||
if (record.backendMetricsStatus === "failed") {
|
||||
cell.textContent = "加载失败";
|
||||
return;
|
||||
}
|
||||
|
||||
if (record.backendMetricsStatus === "missing") {
|
||||
cell.textContent = UNAVAILABLE_BACKEND_METRICS_TEXT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (record.backendMetricsStatus !== "success" || !record.backendMetrics) {
|
||||
cell.textContent = "";
|
||||
return;
|
||||
}
|
||||
|
||||
cell.replaceChildren(createBackendMetricsPanel(cell.ownerDocument, record.backendMetrics));
|
||||
}
|
||||
|
||||
function createBackendMetricsPanel(
|
||||
document: Document,
|
||||
backendMetrics: BackendMetrics
|
||||
): HTMLElement {
|
||||
const panel = document.createElement("div");
|
||||
panel.dataset.marketBackendMetrics = "panel";
|
||||
|
||||
[
|
||||
["看后搜率", backendMetrics.afterViewSearchRate],
|
||||
["看后搜数", backendMetrics.afterViewSearchCount],
|
||||
["新增A3数", backendMetrics.a3IncreaseCount],
|
||||
["新增A3率", backendMetrics.newA3Rate],
|
||||
["CPA3", backendMetrics.cpa3],
|
||||
["cp_search", backendMetrics.cpSearch]
|
||||
].forEach(([label, value]) => {
|
||||
const item = document.createElement("div");
|
||||
item.textContent = `${label}${value ?? ""}`;
|
||||
panel.appendChild(item);
|
||||
});
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
+169
-5
@@ -21,8 +21,9 @@ import {
|
||||
isAuthResponseMessage,
|
||||
type AuthStateValue
|
||||
} from "../../shared/auth-messages";
|
||||
import { createBatchSubmitClient } from "../../shared/batch-submit-client";
|
||||
import { isBackendMetricsResponseMessage } from "../../shared/backend-metrics-messages";
|
||||
import type {
|
||||
BackendMetrics,
|
||||
MarketApiResult,
|
||||
MarketFilterState,
|
||||
MarketExportTarget,
|
||||
@@ -41,6 +42,9 @@ export interface CreateMarketControllerOptions {
|
||||
document: Document;
|
||||
getAuthState?: () => Promise<AuthStateValue>;
|
||||
loadAuthorMetrics?: (authorId: string) => Promise<MarketApiResult>;
|
||||
searchBackendMetrics?: (starIds: string[]) => Promise<
|
||||
Array<BackendMetrics & { starId: string }>
|
||||
>;
|
||||
mutationObserverFactory?: (
|
||||
callback: MutationCallback
|
||||
) => MutationObserverLike;
|
||||
@@ -57,6 +61,9 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
const resultStore = options.resultStore ?? createMarketResultStore();
|
||||
const loadAuthorMetrics =
|
||||
options.loadAuthorMetrics ?? marketApiClient.loadAuthorAseInfo;
|
||||
const searchBackendMetrics =
|
||||
options.searchBackendMetrics ??
|
||||
(hasRuntimeMessageSender() ? (starIds: string[]) => readBackendMetrics(sendRuntimeMessage, starIds) : null);
|
||||
const buildCsv = options.buildCsv ?? buildMarketCsv;
|
||||
const getAuthState = options.getAuthState ?? (() => readAuthState(sendRuntimeMessage));
|
||||
const mutationObserverFactory =
|
||||
@@ -67,10 +74,8 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
(() => options.window.prompt("请输入批次名称"));
|
||||
const submitBatch =
|
||||
options.submitBatch ??
|
||||
createBatchSubmitClient({
|
||||
baseUrl: "http://127.0.0.1:4319",
|
||||
sendMessage: sendRuntimeMessage
|
||||
}).submitBatch;
|
||||
((payload: BatchPayload) =>
|
||||
readBatchSubmitAck(sendRuntimeMessage, payload));
|
||||
const exportRangeController = createExportRangeController({
|
||||
document: options.document,
|
||||
onProgress: ({ currentPage, totalPages }) => {
|
||||
@@ -197,12 +202,21 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pageRows: Array<{
|
||||
rowDom: MarketRowDom;
|
||||
rowSnapshot: MarketRowSnapshot;
|
||||
}> = [];
|
||||
|
||||
for (const rowDom of table.rows) {
|
||||
const rowSnapshot = readRowSnapshot(rowDom);
|
||||
if (!rowSnapshot.authorId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pageRows.push({
|
||||
rowDom,
|
||||
rowSnapshot
|
||||
});
|
||||
resultStore.upsertMarketRow(rowSnapshot);
|
||||
const existingRecord = resultStore.getRecord(rowSnapshot.authorId);
|
||||
if (existingRecord?.status === "success" && existingRecord.rates) {
|
||||
@@ -265,6 +279,86 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
status: "failed"
|
||||
});
|
||||
}
|
||||
|
||||
await hydrateBackendMetricsForPage(pageRows);
|
||||
}
|
||||
|
||||
async function hydrateBackendMetricsForPage(
|
||||
pageRows: Array<{
|
||||
rowDom: MarketRowDom;
|
||||
rowSnapshot: MarketRowSnapshot;
|
||||
}>
|
||||
): Promise<void> {
|
||||
if (!searchBackendMetrics || pageRows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingBackendRows = pageRows.filter(({ rowDom, rowSnapshot }) => {
|
||||
const record = resultStore.getRecord(rowSnapshot.authorId);
|
||||
if (!record) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
record.backendMetricsStatus === "success" ||
|
||||
record.backendMetricsStatus === "missing" ||
|
||||
record.backendMetricsStatus === "failed" ||
|
||||
record.backendMetricsStatus === "loading"
|
||||
) {
|
||||
renderMarketRowState(rowDom, record);
|
||||
return false;
|
||||
}
|
||||
|
||||
resultStore.setBackendMetricsLoading(rowSnapshot.authorId);
|
||||
renderMarketRowState(rowDom, {
|
||||
...record,
|
||||
...rowSnapshot,
|
||||
backendMetricsStatus: "loading"
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
if (pendingBackendRows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const rows = await searchBackendMetrics(
|
||||
pendingBackendRows.map(({ rowSnapshot }) => rowSnapshot.authorId)
|
||||
);
|
||||
const rowMap = new Map(rows.map((row) => [row.starId, row]));
|
||||
|
||||
pendingBackendRows.forEach(({ rowSnapshot }) => {
|
||||
const backendMetrics = rowMap.get(rowSnapshot.authorId);
|
||||
if (backendMetrics) {
|
||||
resultStore.setBackendMetricsSuccess(rowSnapshot.authorId, backendMetrics);
|
||||
} else {
|
||||
resultStore.setBackendMetricsMissing(rowSnapshot.authorId);
|
||||
}
|
||||
|
||||
const record = resultStore.getRecord(rowSnapshot.authorId);
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pageRow = pendingBackendRows.find(
|
||||
(candidate) => candidate.rowSnapshot.authorId === rowSnapshot.authorId
|
||||
);
|
||||
if (pageRow) {
|
||||
renderMarketRowState(pageRow.rowDom, record);
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
pendingBackendRows.forEach(({ rowDom, rowSnapshot }) => {
|
||||
resultStore.setBackendMetricsFailed(rowSnapshot.authorId);
|
||||
const record = resultStore.getRecord(rowSnapshot.authorId);
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderMarketRowState(rowDom, record);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function applyCurrentView(): void {
|
||||
@@ -360,6 +454,11 @@ export function createMarketController(options: CreateMarketControllerOptions) {
|
||||
...existingRecord,
|
||||
...rowSnapshot,
|
||||
authorName: mergeStringValue(existingRecord?.authorName, rowSnapshot.authorName) ?? "",
|
||||
backendMetrics: mergeFieldMap(
|
||||
existingRecord?.backendMetrics,
|
||||
rowSnapshot.backendMetrics
|
||||
),
|
||||
backendMetricsStatus: existingRecord?.backendMetricsStatus ?? "idle",
|
||||
exportFields: mergeFieldMap(
|
||||
existingRecord?.exportFields,
|
||||
rowSnapshot.exportFields
|
||||
@@ -644,6 +743,57 @@ async function readAuthState(
|
||||
return response.value;
|
||||
}
|
||||
|
||||
async function readBatchSubmitAck(
|
||||
sendMessage: (message: unknown) => Promise<unknown>,
|
||||
payload: BatchPayload
|
||||
): Promise<unknown> {
|
||||
const response = await sendMessage({
|
||||
payload,
|
||||
type: "batch:submit"
|
||||
});
|
||||
|
||||
if (
|
||||
response &&
|
||||
typeof response === "object" &&
|
||||
(response as { ok?: unknown }).ok === true
|
||||
) {
|
||||
return (response as { value?: unknown }).value;
|
||||
}
|
||||
|
||||
if (
|
||||
response &&
|
||||
typeof response === "object" &&
|
||||
(response as { ok?: unknown }).ok === false &&
|
||||
typeof (response as { error?: unknown }).error === "string"
|
||||
) {
|
||||
throw new Error((response as { error: string }).error);
|
||||
}
|
||||
|
||||
throw new Error("批次提交失败,请稍后重试");
|
||||
}
|
||||
|
||||
async function readBackendMetrics(
|
||||
sendMessage: (message: unknown) => Promise<unknown>,
|
||||
starIds: string[]
|
||||
): Promise<Array<BackendMetrics & { starId: string }>> {
|
||||
const response = await sendMessage({
|
||||
type: "backend-metrics:search",
|
||||
value: {
|
||||
starIds
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
isBackendMetricsResponseMessage(response) &&
|
||||
response.ok &&
|
||||
response.type === "backend-metrics:result"
|
||||
) {
|
||||
return response.value.rows;
|
||||
}
|
||||
|
||||
throw new Error("后端指标加载失败");
|
||||
}
|
||||
|
||||
function mergeStringValue(
|
||||
current: string | undefined,
|
||||
incoming: string | undefined
|
||||
@@ -658,3 +808,17 @@ function mergeStringValue(
|
||||
function hasTextValue(value: string | undefined): boolean {
|
||||
return typeof value === "string" && value.trim().length > 0;
|
||||
}
|
||||
|
||||
function hasRuntimeMessageSender(): boolean {
|
||||
return Boolean(
|
||||
(
|
||||
globalThis as typeof globalThis & {
|
||||
chrome?: {
|
||||
runtime?: {
|
||||
sendMessage?: unknown;
|
||||
};
|
||||
};
|
||||
}
|
||||
).chrome?.runtime?.sendMessage
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
BackendMetrics,
|
||||
MarketApiFailureReason,
|
||||
MarketRecord,
|
||||
MarketRowSnapshot
|
||||
@@ -25,6 +26,26 @@ export function createMarketResultStore() {
|
||||
existingRecord.status = "loading";
|
||||
delete existingRecord.failureReason;
|
||||
},
|
||||
setBackendMetricsFailed(authorId: string) {
|
||||
const existingRecord = ensureRecord(authorId);
|
||||
existingRecord.backendMetricsStatus = "failed";
|
||||
},
|
||||
setBackendMetricsLoading(authorId: string) {
|
||||
const existingRecord = ensureRecord(authorId);
|
||||
existingRecord.backendMetricsStatus = "loading";
|
||||
},
|
||||
setBackendMetricsMissing(authorId: string) {
|
||||
const existingRecord = ensureRecord(authorId);
|
||||
existingRecord.backendMetricsStatus = "missing";
|
||||
},
|
||||
setBackendMetricsSuccess(authorId: string, backendMetrics: BackendMetrics) {
|
||||
const existingRecord = ensureRecord(authorId);
|
||||
existingRecord.backendMetricsStatus = "success";
|
||||
existingRecord.backendMetrics = {
|
||||
...existingRecord.backendMetrics,
|
||||
...backendMetrics
|
||||
};
|
||||
},
|
||||
setAuthorSuccess(authorId: string, rates: AfterSearchRates) {
|
||||
const existingRecord = ensureRecord(authorId);
|
||||
existingRecord.status = "success";
|
||||
@@ -49,19 +70,24 @@ export function createMarketResultStore() {
|
||||
row.price21To60s
|
||||
);
|
||||
existingRecord.exportFields = mergeFieldMap(
|
||||
existingRecord.exportFields,
|
||||
existingRecord.exportFields,
|
||||
row.exportFields
|
||||
);
|
||||
existingRecord.backendMetrics = mergeFieldMap(
|
||||
existingRecord.backendMetrics,
|
||||
row.backendMetrics
|
||||
);
|
||||
existingRecord.hasDirectRatesSource =
|
||||
existingRecord.hasDirectRatesSource || row.hasDirectRatesSource;
|
||||
existingRecord.rates = mergeFieldMap(existingRecord.rates, row.rates);
|
||||
return existingRecord;
|
||||
}
|
||||
|
||||
const nextRecord: MarketRecord = {
|
||||
...row,
|
||||
status: "idle"
|
||||
};
|
||||
const nextRecord: MarketRecord = {
|
||||
...row,
|
||||
backendMetricsStatus: "idle",
|
||||
status: "idle"
|
||||
};
|
||||
records.set(row.authorId, nextRecord);
|
||||
return nextRecord;
|
||||
}
|
||||
@@ -76,6 +102,7 @@ export function createMarketResultStore() {
|
||||
const nextRecord: MarketRecord = {
|
||||
authorId,
|
||||
authorName: authorId,
|
||||
backendMetricsStatus: "idle",
|
||||
status: "idle"
|
||||
};
|
||||
records.set(authorId, nextRecord);
|
||||
|
||||
@@ -3,11 +3,21 @@ export interface AfterSearchRates {
|
||||
singleVideoAfterSearchRate?: string;
|
||||
}
|
||||
|
||||
export interface BackendMetrics {
|
||||
a3IncreaseCount?: string;
|
||||
afterViewSearchCount?: string;
|
||||
afterViewSearchRate?: string;
|
||||
cpSearch?: string;
|
||||
cpa3?: string;
|
||||
newA3Rate?: string;
|
||||
}
|
||||
|
||||
export type MarketRecordStatus = "idle" | "loading" | "success" | "failed" | "missing";
|
||||
|
||||
export interface MarketRowSnapshot {
|
||||
authorId: string;
|
||||
authorName: string;
|
||||
backendMetrics?: BackendMetrics;
|
||||
exportFields?: Record<string, string>;
|
||||
hasDirectRatesSource?: boolean;
|
||||
location?: string;
|
||||
@@ -16,6 +26,7 @@ export interface MarketRowSnapshot {
|
||||
}
|
||||
|
||||
export interface MarketRecord extends MarketRowSnapshot {
|
||||
backendMetricsStatus?: MarketRecordStatus;
|
||||
status: MarketRecordStatus;
|
||||
failureReason?: MarketApiFailureReason;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user