Release govoplan-campaign v0.1.28: stabilize saving, review and delivery recovery
Module Package Release / publish-packages (push) Successful in 12s
Module Package Release / publish-packages (push) Successful in 12s
This commit is contained in:
+44
-10
@@ -5,6 +5,7 @@ import {
|
||||
type ReferenceOptionProvider
|
||||
} from "@govoplan/core-webui";
|
||||
import { campaignJobsQueryParams, type CampaignJobsQueryParameters } from "../features/campaigns/utils/jobListQuery";
|
||||
import { campaignVersionUpdateForRequest } from "../features/campaigns/utils/editorState";
|
||||
export {
|
||||
fetchResourceAccessExplanation,
|
||||
fetchResourceAccessExplanationSubjects
|
||||
@@ -340,6 +341,7 @@ export type CampaignScheduleCreate = {
|
||||
};
|
||||
|
||||
export type CampaignVersionDetail = CampaignVersionListItem & {
|
||||
review_build_token?: string | null;
|
||||
raw_json: Record<string, unknown>;
|
||||
campaign_json?: Record<string, unknown>;
|
||||
mail_profile_migration_required?: boolean;
|
||||
@@ -936,6 +938,7 @@ export type CampaignSendNowPayload = {
|
||||
};
|
||||
|
||||
export type CampaignAppendSentPayload = {
|
||||
version_id?: string;
|
||||
dry_run?: boolean;
|
||||
enqueue_celery?: boolean;
|
||||
run_inline?: boolean;
|
||||
@@ -1017,6 +1020,7 @@ export type CampaignAttachmentLinkMatchesResponse = {
|
||||
};
|
||||
|
||||
export type CampaignMockSendPayload = {
|
||||
use_reviewed_build?: boolean;
|
||||
version_id?: string | null;
|
||||
send?: boolean;
|
||||
include_warnings?: boolean;
|
||||
@@ -1027,6 +1031,10 @@ export type CampaignMockSendPayload = {
|
||||
};
|
||||
|
||||
export type CampaignReviewStatePayload = {
|
||||
merge_progress?: boolean;
|
||||
build_token?: string | null;
|
||||
base_revision?: number | null;
|
||||
decision_category_key?: string | null;
|
||||
inspection_complete: boolean;
|
||||
reviewed_message_keys: string[];
|
||||
issue_decisions: Array<{
|
||||
@@ -1517,7 +1525,8 @@ options: CampaignWorkspaceQuery = {})
|
||||
export async function getCampaignWorkspaceDelta(
|
||||
settings: ApiSettings,
|
||||
campaignId: string,
|
||||
options: CampaignWorkspaceQuery = {})
|
||||
options: CampaignWorkspaceQuery = {},
|
||||
requestOptions?: Pick<RequestInit, "cache" | "signal">)
|
||||
: Promise<CampaignWorkspaceDeltaResponse> {
|
||||
const params = new URLSearchParams();
|
||||
if (options.versionId) params.set("version_id", options.versionId);
|
||||
@@ -1527,7 +1536,7 @@ options: CampaignWorkspaceQuery = {})
|
||||
if (options.since) params.set("since", options.since);
|
||||
if (options.limit) params.set("limit", String(options.limit));
|
||||
const suffix = params.size > 0 ? `?${params.toString()}` : "";
|
||||
return apiFetch<CampaignWorkspaceDeltaResponse>(settings, `/api/v1/campaigns/${campaignId}/workspace/delta${suffix}`);
|
||||
return apiFetch<CampaignWorkspaceDeltaResponse>(settings, `/api/v1/campaigns/${campaignId}/workspace/delta${suffix}`, requestOptions);
|
||||
}
|
||||
|
||||
export async function listCampaignVersions(
|
||||
@@ -1542,7 +1551,9 @@ settings: ApiSettings,
|
||||
campaignId: string,
|
||||
versionId: string)
|
||||
: Promise<CampaignVersionDetail> {
|
||||
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}`);
|
||||
// Conflict reconciliation and discard/reload require the current revision,
|
||||
// never an older coalesced or short-lived cached read.
|
||||
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}`, { cache: "no-store" });
|
||||
}
|
||||
|
||||
export async function unlockCampaignVersionValidation(
|
||||
@@ -1595,7 +1606,7 @@ ifMatch: string)
|
||||
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}`, {
|
||||
method: "PUT",
|
||||
headers: { "If-Match": ifMatch },
|
||||
body: JSON.stringify(payload)
|
||||
body: JSON.stringify(campaignVersionUpdateForRequest(payload))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1607,7 +1618,7 @@ payload: CampaignVersionUpdatePayload = {})
|
||||
: Promise<CampaignCreateResponse> {
|
||||
return apiFetch<CampaignCreateResponse>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/fork`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload)
|
||||
body: JSON.stringify(campaignVersionUpdateForRequest(payload))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1621,7 +1632,7 @@ ifMatch: string)
|
||||
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/autosave`, {
|
||||
method: "POST",
|
||||
headers: { "If-Match": ifMatch },
|
||||
body: JSON.stringify(payload)
|
||||
body: JSON.stringify(campaignVersionUpdateForRequest(payload))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1719,24 +1730,46 @@ versionId?: string)
|
||||
return apiFetch<CampaignSummary>(settings, `/api/v1/campaigns/${campaignId}/summary${suffix}`);
|
||||
}
|
||||
|
||||
export type CampaignDeliveryProgress = {
|
||||
delivery_mode?: string | null;
|
||||
workflow_state?: string;
|
||||
campaign_id: string;
|
||||
version_id: string;
|
||||
generated_at: string;
|
||||
total_jobs: number;
|
||||
smtp: { total: number; processed: number; accepted: number; active: number; pending: number; failed: number; outcome_unknown: number; excluded: number; paused: number; cancelled: number };
|
||||
imap: { total: number; processed: number; appended: number; active: number; pending: number; failed: number; outcome_unknown: number; excluded: number };
|
||||
status_counts: { send: Record<string, number>; queue: Record<string, number>; imap: Record<string, number> };
|
||||
};
|
||||
|
||||
export async function getCampaignDeliveryProgress(
|
||||
settings: ApiSettings, campaignId: string, versionId: string, signal?: AbortSignal
|
||||
): Promise<CampaignDeliveryProgress> {
|
||||
return apiFetch<CampaignDeliveryProgress>(settings,
|
||||
`/api/v1/campaigns/${campaignId}/delivery-progress?version_id=${encodeURIComponent(versionId)}`,
|
||||
{ cache: "no-store", signal });
|
||||
}
|
||||
|
||||
export async function getCampaignJobs(
|
||||
settings: ApiSettings,
|
||||
campaignId: string,
|
||||
options: CampaignJobsQuery = {})
|
||||
options: CampaignJobsQuery = {},
|
||||
init?: Pick<RequestInit, "cache" | "signal">)
|
||||
: Promise<CampaignJobsResponse> {
|
||||
const params = campaignJobsQueryParams(options);
|
||||
const suffix = params.size > 0 ? `?${params.toString()}` : "";
|
||||
return apiFetch<CampaignJobsResponse>(settings, `/api/v1/campaigns/${campaignId}/jobs${suffix}`);
|
||||
return apiFetch<CampaignJobsResponse>(settings, `/api/v1/campaigns/${campaignId}/jobs${suffix}`, init);
|
||||
}
|
||||
|
||||
export async function getCampaignJobsDelta(
|
||||
settings: ApiSettings,
|
||||
campaignId: string,
|
||||
options: CampaignJobsQuery & {since?: string | null;limit?: number;} = {})
|
||||
options: CampaignJobsQuery & {since?: string | null;limit?: number;} = {},
|
||||
init?: Pick<RequestInit, "cache" | "signal">)
|
||||
: Promise<CampaignJobsDeltaResponse> {
|
||||
const params = campaignJobsQueryParams(options);
|
||||
const suffix = params.size > 0 ? `?${params.toString()}` : "";
|
||||
return apiFetch<CampaignJobsDeltaResponse>(settings, `/api/v1/campaigns/${campaignId}/jobs/delta${suffix}`);
|
||||
return apiFetch<CampaignJobsDeltaResponse>(settings, `/api/v1/campaigns/${campaignId}/jobs/delta${suffix}`, init);
|
||||
}
|
||||
|
||||
export async function getCampaignJobDetail(
|
||||
@@ -1941,6 +1974,7 @@ payload: CampaignAppendSentPayload = {})
|
||||
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/append-sent`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
version_id: payload.version_id,
|
||||
dry_run: payload.dry_run ?? false,
|
||||
enqueue_celery: payload.enqueue_celery ?? true,
|
||||
run_inline: payload.run_inline ?? false
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { apiFetch, type ApiSettings } from "@govoplan/core-webui";
|
||||
|
||||
export type CampaignDeliveryPolicyScope = "system" | "tenant";
|
||||
export type CampaignDeliveryPolicy = {
|
||||
scope: CampaignDeliveryPolicyScope;
|
||||
synchronous_send_max_recipients: number | null;
|
||||
revision: string;
|
||||
max_configurable_recipients: number;
|
||||
effective_max_recipients: number;
|
||||
inherited_max_recipients: number;
|
||||
absolute_max_recipients: number;
|
||||
deployment_ceiling_explicit: boolean;
|
||||
deployment_max_recipients: number;
|
||||
};
|
||||
|
||||
export function getCampaignDeliveryPolicy(settings: ApiSettings, scope: CampaignDeliveryPolicyScope, signal?: AbortSignal) {
|
||||
return apiFetch<CampaignDeliveryPolicy>(settings, `/api/v1/campaigns/settings/delivery-policy/${scope}`, { cache: "no-store", signal });
|
||||
}
|
||||
|
||||
export function saveCampaignDeliveryPolicy(settings: ApiSettings, scope: CampaignDeliveryPolicyScope, value: number | null, revision: string) {
|
||||
return apiFetch<CampaignDeliveryPolicy>(settings, `/api/v1/campaigns/settings/delivery-policy/${scope}`, {
|
||||
method: "PUT", body: JSON.stringify({ synchronous_send_max_recipients: value, expected_revision: revision })
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { apiFetch, type ApiSettings } from "@govoplan/core-webui";
|
||||
|
||||
export type CampaignRecoveryChannel = "smtp" | "imap";
|
||||
export type CampaignInlineRecoveryAction = "retry" | "send-unattempted";
|
||||
export type CampaignInlineRecoveryResult = {
|
||||
selected_count: number;
|
||||
remaining_count?: number;
|
||||
attempted_count?: number;
|
||||
sent_count?: number;
|
||||
failed_count?: number;
|
||||
outcome_unknown_count?: number;
|
||||
enqueued_count?: number;
|
||||
run_inline?: boolean;
|
||||
};
|
||||
|
||||
export async function runCampaignInlineRecovery(settings: ApiSettings, campaignId: string, action: CampaignInlineRecoveryAction, payload: {
|
||||
version_id: string;
|
||||
job_ids: string[];
|
||||
include_permanent?: boolean;
|
||||
}): Promise<CampaignInlineRecoveryResult> {
|
||||
const response = await apiFetch<{ result: CampaignInlineRecoveryResult }>(settings,
|
||||
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/jobs/${action}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ ...payload, run_inline: true, enqueue_celery: false })
|
||||
});
|
||||
return response.result;
|
||||
}
|
||||
|
||||
export async function recoverCampaignJobClaim(settings: ApiSettings, campaignId: string, jobId: string, payload: {
|
||||
channel: CampaignRecoveryChannel;
|
||||
expected_revision: string;
|
||||
note: string;
|
||||
}): Promise<void> {
|
||||
await apiFetch(settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/jobs/${encodeURIComponent(jobId)}/recover-claim`, {
|
||||
method: "POST", body: JSON.stringify(payload)
|
||||
});
|
||||
}
|
||||
+12
-6
@@ -72,8 +72,10 @@ export async function testMailProfileSmtp(
|
||||
serverId?: string | null,
|
||||
credentialId?: string | null,
|
||||
campaignId?: string | null
|
||||
): Promise<MailConnectionTestResponse> {
|
||||
return runProfileAction<MailConnectionTestResponse>(settings, profileId, "smtp", serverId, credentialId, campaignId);
|
||||
): Promise<MailConnectionTestResponse & { protocol: "smtp" }> {
|
||||
const result = await runProfileAction<MailConnectionTestResponse>(settings, profileId, "smtp", serverId, credentialId, campaignId);
|
||||
if (result.protocol !== "smtp") throw new Error("Unexpected protocol in SMTP test response.");
|
||||
return { ...result, protocol: result.protocol };
|
||||
}
|
||||
|
||||
export async function testMailProfileImap(
|
||||
@@ -82,8 +84,10 @@ export async function testMailProfileImap(
|
||||
serverId?: string | null,
|
||||
credentialId?: string | null,
|
||||
campaignId?: string | null
|
||||
): Promise<MailConnectionTestResponse> {
|
||||
return runProfileAction<MailConnectionTestResponse>(settings, profileId, "imap", serverId, credentialId, campaignId);
|
||||
): Promise<MailConnectionTestResponse & { protocol: "imap" }> {
|
||||
const result = await runProfileAction<MailConnectionTestResponse>(settings, profileId, "imap", serverId, credentialId, campaignId);
|
||||
if (result.protocol !== "imap") throw new Error("Unexpected protocol in IMAP test response.");
|
||||
return { ...result, protocol: result.protocol };
|
||||
}
|
||||
|
||||
export async function listMailProfileImapFolders(
|
||||
@@ -92,8 +96,10 @@ export async function listMailProfileImapFolders(
|
||||
serverId?: string | null,
|
||||
credentialId?: string | null,
|
||||
campaignId?: string | null
|
||||
): Promise<MailImapFolderListResponse> {
|
||||
return runProfileAction<MailImapFolderListResponse>(settings, profileId, "folders", serverId, credentialId, campaignId);
|
||||
): Promise<MailImapFolderListResponse & { protocol: "imap" }> {
|
||||
const result = await runProfileAction<MailImapFolderListResponse>(settings, profileId, "folders", serverId, credentialId, campaignId);
|
||||
if (result.protocol !== "imap") throw new Error("Unexpected protocol in IMAP folder response.");
|
||||
return { ...result, protocol: result.protocol };
|
||||
}
|
||||
|
||||
export async function getMockMailboxMessage(settings: ApiSettings, id: string): Promise<MockMailboxMessageResponse> {
|
||||
|
||||
Reference in New Issue
Block a user