diff --git a/SW.Bitween.Api/Domain/Accounts/Role.cs b/SW.Bitween.Api/Domain/Accounts/Role.cs
index 99db343e..08c6193e 100644
--- a/SW.Bitween.Api/Domain/Accounts/Role.cs
+++ b/SW.Bitween.Api/Domain/Accounts/Role.cs
@@ -17,7 +17,7 @@ public class Role : BaseEntity, IAudited
public const int ViewerId = 3;
/// The groups the built-in Member and Viewer roles reach; Administration stays admin-only.
- private static readonly string[] NonAdminGroups = ["Operate", "Integrations", "Configuration"];
+ private static readonly string[] NonAdminGroups = ["Operate", "Subscriptions", "Configuration"];
private Role()
{
diff --git a/SW.Bitween.Sdk/Model/Permissions.cs b/SW.Bitween.Sdk/Model/Permissions.cs
index 083d1bc2..2a31d394 100644
--- a/SW.Bitween.Sdk/Model/Permissions.cs
+++ b/SW.Bitween.Sdk/Model/Permissions.cs
@@ -178,46 +178,46 @@ private static PermissionAreaModel Area(string id, string label, string group, s
Area("dashboard", "Dashboard", "Operate", "Traffic and health overview (reached from the logo).",
(View, "See the dashboard.")),
- // ——— Integrations ———
- Area("subscriptions", "Integrations", "Integrations", "The configured pipelines that process exchanges.",
- (View, "Browse integrations and their configuration."),
- (Create, "Create integrations."),
+ // ——— Subscriptions ———
+ Area("subscriptions", "Subscriptions", "Subscriptions", "The configured pipelines that process exchanges.",
+ (View, "Browse subscriptions and their configuration."),
+ (Create, "Create subscriptions."),
(Edit, "Change adapters, mappings and settings."),
- (Delete, "Delete integrations."),
+ (Delete, "Delete subscriptions."),
(Operate, "Pause, resume, receive now, aggregate now.")),
- Area("partners", "Partners", "Integrations", "The external parties you exchange data with.",
+ Area("partners", "Partners", "Subscriptions", "The external parties you exchange data with.",
(View, "Browse partners and their properties."),
(Create, "Create partners."),
(Edit, "Change partner details, properties and API keys."),
(Delete, "Delete partners.")),
- Area("documents", "Information types", "Integrations",
+ Area("documents", "Information types", "Subscriptions",
"The kinds of business documents that flow between partners.",
(View, "Browse information types."),
(Create, "Create information types."),
(Edit, "Change information types, codes and promoted properties."),
(Delete, "Delete unused information types.")),
- Area("global-values", "Global values", "Integrations", "Shared value sets adapters can reference.",
+ Area("global-values", "Global values", "Subscriptions", "Shared value sets adapters can reference.",
(View, "Browse global value sets."),
(Create, "Create value sets."),
(Edit, "Change value sets."),
(Delete, "Delete value sets.")),
- Area("notifiers", "Notifiers", "Integrations", "Alerts sent when exchanges fail or succeed.",
+ Area("notifiers", "Notifiers", "Subscriptions", "Alerts sent when exchanges fail or succeed.",
(View, "Browse notifiers and their delivery history."),
(Create, "Create notifiers."),
(Edit, "Change notifiers."),
(Delete, "Remove notifiers.")),
- Area("api-gateways", "API gateways", "Integrations", "HTTP entry points partners call into.",
+ Area("api-gateways", "API gateways", "Subscriptions", "HTTP entry points partners call into.",
(View, "Browse API gateways and attached partners."),
(Create, "Create new API gateways."),
(Edit, "Change gateways and partner attachments."),
(Delete, "Delete API gateways.")),
- Area("bus-gateways", "Bus gateways", "Integrations", "Bus listeners that route documents to integrations.",
+ Area("bus-gateways", "Bus gateways", "Subscriptions", "Bus listeners that route documents to subscriptions.",
(View, "Browse bus gateways and routes."),
(Create, "Create new bus gateways."),
(Edit, "Change gateways and routes."),
diff --git a/SW.Bitween.Web/ClientApp/e2e/exchanges.spec.ts b/SW.Bitween.Web/ClientApp/e2e/exchanges.spec.ts
index 7ba60137..21299294 100644
--- a/SW.Bitween.Web/ClientApp/e2e/exchanges.spec.ts
+++ b/SW.Bitween.Web/ClientApp/e2e/exchanges.spec.ts
@@ -44,9 +44,9 @@ test("exchanges list, filter, retry, bulk retry, create", async ({ page }) => {
await page.getByRole("dialog").getByRole("button", { name: "Retry" }).click();
await expect(page.getByRole("dialog")).toHaveCount(0, { timeout: 15000 });
- // Manually create an exchange addressed at an integration.
+ // Manually create an exchange addressed at a subscription.
await page.goto("exchanges/new");
- await page.getByRole("combobox", { name: "Pick an integration…" }).click();
+ await page.getByRole("combobox", { name: "Pick a subscription…" }).click();
await page.getByRole("option", { name: "s3 test sub" }).click();
// Dismiss the dropdown panel via an outside click (it sits above the panel's
// anchor point, so it can't itself be covered) rather than Escape, which
diff --git a/SW.Bitween.Web/ClientApp/e2e/gateways.spec.ts b/SW.Bitween.Web/ClientApp/e2e/gateways.spec.ts
index da8358d6..12b9617b 100644
--- a/SW.Bitween.Web/ClientApp/e2e/gateways.spec.ts
+++ b/SW.Bitween.Web/ClientApp/e2e/gateways.spec.ts
@@ -11,7 +11,7 @@ test.beforeEach(async ({ page }) => {
await page.waitForURL((url) => !url.pathname.endsWith("/login"), { timeout: 15000 });
});
-test("API gateway: create, attach partner, create integration detour, edit attachment, detach, delete", async ({
+test("API gateway: create, attach partner, create subscription detour, edit attachment, detach, delete", async ({
page,
}) => {
const name = `Playwright API GW ${Date.now()}`;
@@ -22,38 +22,38 @@ test("API gateway: create, attach partner, create integration detour, edit attac
await expect(page).toHaveURL(/\/api-gateways\/\d+$/);
await expect(page.getByRole("heading", { name })).toBeVisible();
- // Attach a partner, detouring to create the required GatewayApiCall integration inline.
+ // Attach a partner, detouring to create the required GatewayApiCall subscription inline.
await page.getByRole("button", { name: "Attach partner" }).click();
await expect(page).toHaveURL(/\/api-gateways\/\d+\/attach$/);
await page.getByRole("button", { name: "acme" }).click();
await page.getByRole("button", { name: "Continue" }).click();
- const integrationName = `Playwright GW Integration ${Date.now()}`;
- await page.getByRole("link", { name: "New integration" }).click();
+ const subscriptionName = `Playwright GW Subscription ${Date.now()}`;
+ await page.getByRole("link", { name: "New subscription" }).click();
await expect(page).toHaveURL(/\/subscriptions\/new\?type=GatewayApiCall/);
- await page.fill("#ni-name", integrationName);
+ await page.fill("#ni-name", subscriptionName);
await page.getByRole("button", { name: "test doc" }).click();
await page.getByLabel("handler adapter").click();
await page.getByRole("option", { name: "NativeHttpHandler" }).click();
await expect(page.getByRole("listbox")).toHaveCount(0, { timeout: 10000 });
await page.locator("#prop-Url").fill("https://example.com/sink");
- await page.getByRole("button", { name: "Create integration" }).click();
+ await page.getByRole("button", { name: "Create subscription" }).click();
// This page itself renders a ReturnBanner with a "Continue" button before
// the mutation resolves (inherited from the detour link) — wait for the
- // create to actually land on the new integration's own page first, or the
+ // create to actually land on the new subscription's own page first, or the
// click races and hits that stale button instead.
await expect(page).toHaveURL(/\/subscriptions\/\d+\?/);
await page.getByRole("button", { name: "Continue" }).click();
await expect(page).toHaveURL(/\/api-gateways\/\d+\/attach$/);
- await expect(page.getByText(integrationName)).toBeVisible();
+ await expect(page.getByText(subscriptionName)).toBeVisible();
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Attach partner" }).click();
await expect(page).toHaveURL(/\/api-gateways\/\d+$/);
await expect(page.getByText("acme").first()).toBeVisible();
- await expect(page.getByText(integrationName)).toBeVisible();
+ await expect(page.getByText(subscriptionName)).toBeVisible();
// Edit the attachment — exercises the remove-then-add path (backend's
// updatepartner can't mutate a composite-key column in place).
@@ -61,7 +61,7 @@ test("API gateway: create, attach partner, create integration detour, edit attac
await expect(page).toHaveURL(/\/api-gateways\/\d+\/attachments\/\d+$/);
await page.getByRole("button", { name: "Save" }).click();
await expect(page).toHaveURL(/\/api-gateways\/\d+$/);
- await expect(page.getByText(integrationName)).toBeVisible();
+ await expect(page.getByText(subscriptionName)).toBeVisible();
// Detach.
await page.getByRole("button", { name: "Detach acme" }).click();
@@ -80,7 +80,7 @@ test("API gateway: create, attach partner, create integration detour, edit attac
.getByRole("button", { name: "Delete gateway" })
.click();
// ApiGatewayPage navigates to /api-gateways, which the router redirects to
- // the unified integrations list.
+ // the unified subscriptions list.
await expect(page).toHaveURL(/\/subscriptions\?types=api-gateways$/);
});
@@ -103,28 +103,28 @@ test("Bus gateway: create, add route with match expression, edit route, remove,
await page.getByRole("button", { name: "No partner" }).click();
await page.getByRole("button", { name: "Continue" }).click();
- // Integration step — detour to create the required BusGateway integration.
- const integrationName = `Playwright Bus Integration ${Date.now()}`;
- await page.getByRole("link", { name: "New integration" }).click();
+ // Subscription step — detour to create the required BusGateway subscription.
+ const subscriptionName = `Playwright Bus Subscription ${Date.now()}`;
+ await page.getByRole("link", { name: "New subscription" }).click();
await expect(page).toHaveURL(/\/subscriptions\/new\?type=BusGateway/);
- await page.fill("#ni-name", integrationName);
+ await page.fill("#ni-name", subscriptionName);
await page.getByLabel("handler adapter").click();
await page.getByRole("option", { name: "NativeHttpHandler" }).click();
await expect(page.getByRole("listbox")).toHaveCount(0, { timeout: 10000 });
await page.locator("#prop-Url").fill("https://example.com/sink");
- await page.getByRole("button", { name: "Create integration" }).click();
+ await page.getByRole("button", { name: "Create subscription" }).click();
// Wait for the create to actually land (see the comment in the API gateway
// test above) before clicking the ReturnBanner's "Continue".
await expect(page).toHaveURL(/\/subscriptions\/\d+\?/);
await page.getByRole("button", { name: "Continue" }).click();
await expect(page).toHaveURL(/\/bus-gateways\/\d+\/add-route$/);
- await expect(page.getByText(integrationName)).toBeVisible();
+ await expect(page.getByText(subscriptionName)).toBeVisible();
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Add route" }).click();
await expect(page).toHaveURL(/\/bus-gateways\/\d+$/);
- await expect(page.getByText(integrationName)).toBeVisible();
+ await expect(page.getByText(subscriptionName)).toBeVisible();
// Edit the route (no-op save exercises the round trip of a null match expression).
await page.getByRole("button", { name: /Edit route \d+/ }).click();
diff --git a/SW.Bitween.Web/ClientApp/e2e/permissions-enforcement.spec.ts b/SW.Bitween.Web/ClientApp/e2e/permissions-enforcement.spec.ts
index b2e29b85..675cb5eb 100644
--- a/SW.Bitween.Web/ClientApp/e2e/permissions-enforcement.spec.ts
+++ b/SW.Bitween.Web/ClientApp/e2e/permissions-enforcement.spec.ts
@@ -51,7 +51,7 @@ test("a custom role grants exactly what was ticked, in the nav, by URL, and at t
// 1. The sidebar offers the one page they can see, and nothing else.
await expect(sidebarLinks(page).filter({ hasText: "Exchanges" })).toBeVisible();
- for (const hidden of ["Partners", "Integrations", "Work groups", "Team", "Settings"])
+ for (const hidden of ["Partners", "Subscriptions", "Work groups", "Team", "Settings"])
await expect(sidebarLinks(page).filter({ hasText: hidden })).toHaveCount(0);
// 2. Typing the URL of a page they lack doesn't get them in.
@@ -109,7 +109,7 @@ test("Viewer can read but not write", async ({ page }) => {
await removeMember(page, email);
});
-test("Member can configure integrations but not manage the team", async ({ page }) => {
+test("Member can configure subscriptions but not manage the team", async ({ page }) => {
await signInAsAdmin(page);
const email = await addMember(page, { name: "Regular Member", roles: ["Member"] });
diff --git a/SW.Bitween.Web/ClientApp/e2e/integrations.spec.ts b/SW.Bitween.Web/ClientApp/e2e/subscriptions.spec.ts
similarity index 92%
rename from SW.Bitween.Web/ClientApp/e2e/integrations.spec.ts
rename to SW.Bitween.Web/ClientApp/e2e/subscriptions.spec.ts
index d680e71a..26c1818b 100644
--- a/SW.Bitween.Web/ClientApp/e2e/integrations.spec.ts
+++ b/SW.Bitween.Web/ClientApp/e2e/subscriptions.spec.ts
@@ -44,12 +44,12 @@ test("scheduled job create, adapters, pause/resume, receive now, list, delete",
// Pause / resume.
await page.getByRole("button", { name: "Pause" }).click();
- await page.getByRole("dialog", { name: "Pause this integration?" }).getByRole("button", { name: "Pause" }).click();
+ await page.getByRole("dialog", { name: "Pause this subscription?" }).getByRole("button", { name: "Pause" }).click();
await expect(page.getByRole("dialog")).toHaveCount(0);
await expect(page.getByText("Paused", { exact: true }).first()).toBeVisible();
await page.getByRole("button", { name: "Resume" }).click();
- await page.getByRole("dialog", { name: "Resume this integration?" }).getByRole("button", { name: "Resume" }).click();
+ await page.getByRole("dialog", { name: "Resume this subscription?" }).getByRole("button", { name: "Resume" }).click();
await expect(page.getByRole("dialog")).toHaveCount(0);
await expect(page.getByText("Paused", { exact: true })).toHaveCount(0);
@@ -73,7 +73,7 @@ test("scheduled job create, adapters, pause/resume, receive now, list, delete",
await row.getByRole("button", { name: `Open ${name}` }).click();
await expect(page).toHaveURL(/\/subscriptions\/\d+$/);
await page.getByRole("button", { name: "Delete" }).click();
- await page.getByRole("button", { name: "Delete integration" }).click();
+ await page.getByRole("button", { name: "Delete subscription" }).click();
await expect(page).toHaveURL(/\/subscriptions$/);
await expect(page.getByText(name)).toHaveCount(0);
});
diff --git a/SW.Bitween.Web/ClientApp/e2e/view-guards.spec.ts b/SW.Bitween.Web/ClientApp/e2e/view-guards.spec.ts
index be9133a7..7d339a18 100644
--- a/SW.Bitween.Web/ClientApp/e2e/view-guards.spec.ts
+++ b/SW.Bitween.Web/ClientApp/e2e/view-guards.spec.ts
@@ -91,19 +91,19 @@ test("lookup mode stays readable, because pickers across the app depend on it",
});
test("a page still loads when the area behind its Used by count is refused", async ({ page }) => {
- const roleName = `PW No Integrations ${Date.now()}`;
+ const roleName = `PW No Subscriptions ${Date.now()}`;
await signInAsAdmin(page);
await createRole(page, {
name: roleName,
permissions: [{ area: "Information types", action: "View" }],
});
- const email = await addMember(page, { name: "No Integrations", roles: [roleName] });
+ const email = await addMember(page, { name: "No Subscriptions", roles: [roleName] });
await signOut(page);
await signIn(page, email, FIRST_PASSWORD);
- // The information types list counts how many integrations use each type, which needs the
- // integrations list this role can't read. The count is what's expendable, not the page.
+ // The information types list counts how many subscriptions use each type, which needs the
+ // subscriptions list this role can't read. The count is what's expendable, not the page.
await page.goto("information-types");
await expect(page.getByText("You don't have access to this page")).toHaveCount(0);
await expect(page.getByRole("table")).toBeVisible();
diff --git a/SW.Bitween.Web/ClientApp/src/api/client.ts b/SW.Bitween.Web/ClientApp/src/api/client.ts
index c861fbae..21f919fe 100644
--- a/SW.Bitween.Web/ClientApp/src/api/client.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/client.ts
@@ -17,13 +17,13 @@ import type {
InformationType,
InformationTypeDetail,
InformationTypeRow,
- Integration,
- IntegrationDetail,
- IntegrationInfo,
- IntegrationLastRun,
- IntegrationRow,
- IntegrationRun,
- IntegrationType,
+ Subscription,
+ SubscriptionDetail,
+ SubscriptionInfo,
+ SubscriptionLastRun,
+ SubscriptionRow,
+ SubscriptionRun,
+ SubscriptionType,
MatchGroup,
Notifier,
NotifierDetail,
@@ -157,24 +157,24 @@ export interface ApiClient {
): Promise;
deleteValueSet(id: string): Promise;
- // — integrations (light summaries; cache aggressively) —
- listIntegrations(): Promise;
+ // — subscriptions (light summaries; cache aggressively) —
+ listSubscriptions(): Promise;
- // — integrations —
- listIntegrationRows(): Promise;
- searchIntegrationRows(query: {
+ // — subscriptions —
+ listSubscriptionRows(): Promise;
+ searchSubscriptionRows(query: {
search: string;
- type: IntegrationType | null;
+ type: SubscriptionType | null;
informationTypeId?: number | null;
partnerId?: number | null;
inactive?: boolean | null;
offset: number;
limit: number;
- }): Promise>;
- getIntegration(id: number): Promise;
- /** One call, one transaction: the integration exists as asked for, or not at all. */
- createIntegration(input: {
- type: IntegrationType;
+ }): Promise>;
+ getSubscription(id: number): Promise;
+ /** One call, one transaction: the subscription exists as asked for, or not at all. */
+ createSubscription(input: {
+ type: SubscriptionType;
name: string;
informationTypeId: number;
/** Required by the types that carry their own partner — Internal and ApiCall. */
@@ -189,15 +189,15 @@ export interface ApiClient {
handlerProperties?: Record;
schedules?: Schedule[];
retryPolicyId?: number | null;
- responseIntegrationId?: number | null;
+ responseSubscriptionId?: number | null;
responseMessageTypeName?: string | null;
enabled?: boolean;
- }): Promise;
- updateIntegration(
+ }): Promise;
+ updateSubscription(
id: number,
changes: Partial<
Pick<
- Integration,
+ Subscription,
| "name"
| "enabled"
| "workGroupId"
@@ -212,24 +212,24 @@ export interface ApiClient {
| "handlerProperties"
| "matchExpression"
| "schedules"
- | "responseIntegrationId"
+ | "responseSubscriptionId"
| "responseMessageTypeName"
>
>,
- ): Promise;
- deleteIntegration(id: number): Promise;
- /** Toggles paused: paused integrations accept work but hold it. */
- pauseIntegration(id: number): Promise;
- receiveNow(id: number): Promise;
- /** Run history for one scheduled integration, newest first. Empty for unscheduled types. */
- listIntegrationRuns(id: number, limit?: number): Promise;
+ ): Promise;
+ deleteSubscription(id: number): Promise;
+ /** Toggles paused: paused subscriptions accept work but hold it. */
+ pauseSubscription(id: number): Promise;
+ receiveNow(id: number): Promise;
+ /** Run history for one scheduled subscription, newest first. Empty for unscheduled types. */
+ listSubscriptionRuns(id: number, limit?: number): Promise;
searchReceiveAttempts(
subscriptionId: number,
query: { outcome: ReceiveOutcome | null; offset: number; limit: number },
): Promise>;
- /** Newest run of every scheduled integration — one request for a whole list. */
- listLastRuns(): Promise;
- /** Will these schedules actually fire? Asks the scheduler, not the integration record. */
+ /** Newest run of every scheduled subscription — one request for a whole list. */
+ listLastRuns(): Promise;
+ /** Will these schedules actually fire? Asks the scheduler, not the subscription record. */
listScheduleHealth(): Promise;
listAdapters(kind: AdapterKind): Promise;
@@ -263,9 +263,9 @@ export interface ApiClient {
changes: { name: string; urlName: string; inactive: boolean },
): Promise;
deleteApiGateway(id: number): Promise;
- /** The integration is either an existing id or defined inline; the endpoint commits both as one. */
+ /** The subscription is either an existing id or defined inline; the endpoint commits both as one. */
attachGatewayPartner(id: number, input: AttachPartnerInput): Promise;
- updateGatewayAttachment(id: number, input: { partnerId: number; integrationId: number }): Promise;
+ updateGatewayAttachment(id: number, input: { partnerId: number; subscriptionId: number }): Promise;
removeGatewayAttachment(id: number, partnerId: number): Promise;
// — bus gateways —
@@ -281,12 +281,12 @@ export interface ApiClient {
createBusGateway(input: { name: string; informationTypeId: number }): Promise;
updateBusGateway(id: number, changes: { name: string; inactive: boolean }): Promise;
deleteBusGateway(id: number): Promise;
- /** The integration is either an existing id or defined inline; the endpoint commits both as one. */
+ /** The subscription is either an existing id or defined inline; the endpoint commits both as one. */
addBusRoute(id: number, input: AddBusRouteInput): Promise;
updateBusRoute(
id: number,
routeId: number,
- input: { integrationId: number; partnerId: number | null; matchExpression: MatchGroup | null },
+ input: { subscriptionId: number; partnerId: number | null; matchExpression: MatchGroup | null },
): Promise;
removeBusRoute(id: number, routeId: number): Promise;
@@ -317,24 +317,24 @@ export interface ApiClient {
attempts: number;
}): Promise;
- /** Spent budget and alert routing for every integration-and-group pair under this policy. */
+ /** Spent budget and alert routing for every subscription-and-group pair under this policy. */
getRetryUsage(policyId: number): Promise;
/**
- * The same report for one integration, which is the only way to reach one whose policy is an
+ * The same report for one subscription, which is the only way to reach one whose policy is an
* inline `CustomRetryPolicy` — those carry no policy id for the policy-scoped report to address,
* yet still spend budget and can sit stopped with no counter anyone can see.
*/
- getIntegrationRetryUsage(integrationId: number): Promise;
- /** The failures one group caught for one integration — what its spent budget went on. */
- getRetryAttempts(policyId: number, pair: { integrationId: number; groupId: string }): Promise;
+ getSubscriptionRetryUsage(subscriptionId: number): Promise;
+ /** The failures one group caught for one subscription — what its spent budget went on. */
+ getRetryAttempts(policyId: number, pair: { subscriptionId: number; groupId: string }): Promise;
/** Hands a spent budget back so the group retries again. Omit a field to reset across it. */
- resetRetryUsage(policyId: number, pair?: { integrationId?: number; groupId?: string }): Promise;
- /** Reset by integration, for the inline-policy case the policy-scoped reset cannot reach. */
- resetIntegrationRetryUsage(integrationId: number, groupId?: string): Promise;
+ resetRetryUsage(policyId: number, pair?: { subscriptionId?: number; groupId?: string }): Promise;
+ /** Reset by subscription, for the inline-policy case the policy-scoped reset cannot reach. */
+ resetSubscriptionRetryUsage(subscriptionId: number, groupId?: string): Promise;
/** Sets, changes or clears where one pair's alert goes — the most specific level. */
saveRetryAlertOverride(
policyId: number,
- input: { integrationId: number; groupId: string } & RetryAlertConfig,
+ input: { subscriptionId: number; groupId: string } & RetryAlertConfig,
): Promise;
// — settings —
@@ -357,17 +357,17 @@ export interface ApiClient {
getExchangeDocument(key: string): Promise;
/**
* Re-runs an exchange from its input file. `reset` re-resolves adapter
- * properties from the integration's current configuration instead of the
+ * properties from the subscription's current configuration instead of the
* values captured when the exchange first ran. Fails with
* AUTO_RETRY_SCHEDULED when an auto-retry is already pending.
*/
retryExchange(id: string, opts: { reset: boolean }): Promise<{ id: string }>;
/** Retries many; exchanges with a pending auto-retry are skipped, not failed. */
bulkRetryExchanges(ids: string[], opts: { reset: boolean }): Promise<{ retried: number; skipped: number }>;
- /** Manually injects a payload, addressed at an integration or an information type. */
+ /** Manually injects a payload, addressed at a subscription or an information type. */
createExchange(input: {
- target: "integration" | "informationType";
- integrationId?: number;
+ target: "subscription" | "informationType";
+ subscriptionId?: number;
informationTypeId?: number;
data: string;
}): Promise<{ id: string }>;
diff --git a/SW.Bitween.Web/ClientApp/src/api/http/dashboard.ts b/SW.Bitween.Web/ClientApp/src/api/http/dashboard.ts
index 7a7279c1..426e2305 100644
--- a/SW.Bitween.Web/ClientApp/src/api/http/dashboard.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/http/dashboard.ts
@@ -1,6 +1,6 @@
import type { ApiClient } from "../client";
import type { DashboardData, ExchangeStatus } from "../types";
-import { integrationMethods } from "./integrations";
+import { subscriptionMethods } from "./subscriptions";
import { get } from "./request";
// ——— backend shapes (camelCase over the wire) ———
@@ -40,18 +40,18 @@ export const dashboardMethods = {
// request, and exact rather than approximated. Bounded to a generous page
// size for what's a modest-scale ops tool; a very high-volume deployment
// would need real pagination here.
- const [xchangeRes, delayedRes, alertsRaw, integrationRows] = await Promise.all([
+ const [xchangeRes, delayedRes, alertsRaw, subscriptionRows] = await Promise.all([
get>(
`/xchanges?filter=${encodeURIComponent(`StartedOn:6:${new Date(windowStart).toISOString()}`)}&size=1000&sort=StartedOn:1`,
),
get>("/delayedretries?size=1"),
get("/ops/alerts"),
- integrationMethods.listIntegrationRows(),
+ subscriptionMethods.listSubscriptionRows(),
]);
const rows = xchangeRes.result;
const startedAt = (r: RawXchangeForDashboard) => Date.parse(r.startedOn);
- const integrationNameById = new Map(integrationRows.map((i) => [i.id, i.name]));
+ const subscriptionNameById = new Map(subscriptionRows.map((i) => [i.id, i.name]));
const todayRows = rows.filter((r) => startedAt(r) >= startOfTodayUtc);
const yesterdayRows = rows.filter((r) => {
@@ -81,16 +81,16 @@ export const dashboardMethods = {
};
});
- const byIntegration = new Map();
+ const bySubscription = new Map();
for (const r of week) {
if (r.subscriptionId === null) continue;
- const entry = byIntegration.get(r.subscriptionId) ?? { count: 0, failed: 0 };
+ const entry = bySubscription.get(r.subscriptionId) ?? { count: 0, failed: 0 };
entry.count++;
if (isBad(r)) entry.failed++;
- byIntegration.set(r.subscriptionId, entry);
+ bySubscription.set(r.subscriptionId, entry);
}
- const busiest = [...byIntegration.entries()]
- .map(([id, v]) => ({ id, name: integrationNameById.get(id) ?? `#${id}`, ...v }))
+ const busiest = [...bySubscription.entries()]
+ .map(([id, v]) => ({ id, name: subscriptionNameById.get(id) ?? `#${id}`, ...v }))
.sort((a, b) => b.count - a.count)
.slice(0, 5);
@@ -101,8 +101,8 @@ export const dashboardMethods = {
.map((r) => ({
id: r.id,
status: toStatus(r),
- integrationId: r.subscriptionId,
- integrationName: r.subscriptionId !== null ? (integrationNameById.get(r.subscriptionId) ?? null) : null,
+ subscriptionId: r.subscriptionId,
+ subscriptionName: r.subscriptionId !== null ? (subscriptionNameById.get(r.subscriptionId) ?? null) : null,
informationTypeCode: r.documentName,
on: r.startedOn,
exception: r.exception,
@@ -122,10 +122,10 @@ export const dashboardMethods = {
busiest,
latestFailures,
attention: {
- failingIntegrations: integrationRows
+ failingSubscriptions: subscriptionRows
.filter((i) => i.consecutiveFailures > 0)
.map((i) => ({ id: i.id, name: i.name, consecutiveFailures: i.consecutiveFailures })),
- pausedIntegrations: integrationRows.filter((i) => i.paused).map((i) => ({ id: i.id, name: i.name })),
+ pausedSubscriptions: subscriptionRows.filter((i) => i.paused).map((i) => ({ id: i.id, name: i.name })),
},
};
},
diff --git a/SW.Bitween.Web/ClientApp/src/api/http/documents.ts b/SW.Bitween.Web/ClientApp/src/api/http/documents.ts
index 9e1652ee..0bed1fd0 100644
--- a/SW.Bitween.Web/ClientApp/src/api/http/documents.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/http/documents.ts
@@ -4,7 +4,7 @@ import type {
InformationTypeDetail,
InformationTypeFormat,
InformationTypeRow,
- IntegrationType,
+ SubscriptionType,
Paged,
TrailEntry,
} from "../types";
@@ -43,7 +43,7 @@ interface RawTrailEntry {
createdBy: string;
}
-const SUB_TYPE_BY_NUM: Record = {
+const SUB_TYPE_BY_NUM: Record = {
1: "Internal",
2: "ApiCall",
4: "Receiving",
@@ -51,7 +51,7 @@ const SUB_TYPE_BY_NUM: Record = {
16: "GatewayApiCall",
32: "BusGateway",
};
-const INTEGRATION_TYPES: IntegrationType[] = [
+const SUBSCRIPTION_TYPES: SubscriptionType[] = [
"Receiving",
"GatewayApiCall",
"BusGateway",
@@ -60,9 +60,9 @@ const INTEGRATION_TYPES: IntegrationType[] = [
"Aggregation",
];
/** Enums may arrive as the numeric value or the name in any case. */
-const toIntegrationType = (t: number | string): IntegrationType => {
+const toSubscriptionType = (t: number | string): SubscriptionType => {
if (typeof t === "number") return SUB_TYPE_BY_NUM[t] ?? "Internal";
- return INTEGRATION_TYPES.find((k) => k.toLowerCase() === t.toLowerCase()) ?? "Internal";
+ return SUBSCRIPTION_TYPES.find((k) => k.toLowerCase() === t.toLowerCase()) ?? "Internal";
};
async function fetchSubscriptionsByDocument(documentId: number): Promise {
@@ -108,7 +108,7 @@ async function fetchDetail(id: number): Promise {
]);
return {
...toInformationType(d),
- integrationSetups: subs.map((s) => ({ id: s.id, name: s.name, type: toIntegrationType(s.type) })),
+ subscriptionSetups: subs.map((s) => ({ id: s.id, name: s.name, type: toSubscriptionType(s.type) })),
busGateways: busGateways
.filter((g) => g.informationTypeId === id)
.map((g) => ({ gatewayId: g.id, gatewayName: g.name })),
diff --git a/SW.Bitween.Web/ClientApp/src/api/http/exchanges.ts b/SW.Bitween.Web/ClientApp/src/api/http/exchanges.ts
index 643236f1..7cf934b4 100644
--- a/SW.Bitween.Web/ClientApp/src/api/http/exchanges.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/http/exchanges.ts
@@ -73,8 +73,8 @@ const STATUS_FILTER: Record = {
const toExchangeRow = (raw: RawXchangeRow, partnerNameById: Map): ExchangeRow => ({
id: raw.id,
status: deriveStatus(raw),
- integrationId: raw.subscriptionId,
- integrationName: raw.subscriptionName,
+ subscriptionId: raw.subscriptionId,
+ subscriptionName: raw.subscriptionName,
informationTypeId: raw.documentId,
informationTypeCode: raw.documentName,
partnerId: raw.partnerId,
@@ -103,8 +103,8 @@ const toExchangeRow = (raw: RawXchangeRow, partnerNameById: Map)
const toScheduledRetryRow = (raw: RawDelayedRetryRow): ScheduledRetryRow => ({
id: raw.id,
on: raw.on,
- integrationId: raw.subscriptionId,
- integrationName: raw.subscriptionName,
+ subscriptionId: raw.subscriptionId,
+ subscriptionName: raw.subscriptionName,
informationTypeId: raw.documentId,
informationTypeCode: raw.documentName,
exception: raw.exception,
@@ -126,7 +126,7 @@ const toScheduledRetryRow = (raw: RawDelayedRetryRow): ScheduledRetryRow => ({
function buildExchangeQuery(query: ExchangeQuery): string {
const params = new URLSearchParams();
if (query.status) params.append("filter", `StatusFilter:1:${STATUS_FILTER[query.status]}`);
- if (query.integrationId !== undefined) params.append("filter", `SubscriptionId:1:${query.integrationId}`);
+ if (query.subscriptionId !== undefined) params.append("filter", `SubscriptionId:1:${query.subscriptionId}`);
if (query.partnerId !== undefined) params.append("filter", `PartnerId:1:${query.partnerId}`);
if (query.informationTypeId !== undefined) params.append("filter", `DocumentId:1:${query.informationTypeId}`);
if (query.ids?.trim()) {
@@ -150,7 +150,7 @@ function buildExchangeQuery(query: ExchangeQuery): string {
function buildScheduledRetryQuery(query: ScheduledRetryQuery): string {
const params = new URLSearchParams();
- if (query.integrationId !== undefined) params.append("filter", `SubscriptionId:1:${query.integrationId}`);
+ if (query.subscriptionId !== undefined) params.append("filter", `SubscriptionId:1:${query.subscriptionId}`);
if (query.informationTypeId !== undefined) params.append("filter", `DocumentId:1:${query.informationTypeId}`);
if (query.exception?.trim()) params.append("filter", `Exception:4:${query.exception.trim()}`);
if (query.from) params.append("filter", `On:6:${query.from}`);
@@ -206,23 +206,23 @@ export const exchangeMethods = {
},
async createExchange(input: {
- target: "integration" | "informationType";
- integrationId?: number;
+ target: "subscription" | "informationType";
+ subscriptionId?: number;
informationTypeId?: number;
data: string;
}): Promise<{ id: string }> {
const filter =
- input.target === "integration"
- ? `SubscriptionId:1:${input.integrationId}`
+ input.target === "subscription"
+ ? `SubscriptionId:1:${input.subscriptionId}`
: `DocumentId:1:${input.informationTypeId}`;
await post("/xchanges", {
- option: input.target === "integration" ? "SubscriberId" : "DocumentId",
- subscriberId: input.target === "integration" ? input.integrationId : null,
+ option: input.target === "subscription" ? "SubscriberId" : "DocumentId",
+ subscriberId: input.target === "subscription" ? input.subscriptionId : null,
documentId: input.target === "informationType" ? input.informationTypeId : null,
data: input.data,
});
// Create.cs returns null too — look up the exchange it just created. When
- // addressed at an information type, every matching integration gets its
+ // addressed at an information type, every matching subscription gets its
// own exchange; we can only link to one, so take the newest.
const res = await get>(
`/xchanges?filter=${encodeURIComponent(filter)}&sort=StartedOn:2&size=1`,
diff --git a/SW.Bitween.Web/ClientApp/src/api/http/gateways.ts b/SW.Bitween.Web/ClientApp/src/api/http/gateways.ts
index ef76bcdd..ffff5ece 100644
--- a/SW.Bitween.Web/ClientApp/src/api/http/gateways.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/http/gateways.ts
@@ -8,12 +8,12 @@ import type {
BusGatewayDetail,
BusGatewayRoute,
BusGatewayRow,
- InlineIntegrationDraft,
+ InlineSubscriptionDraft,
MatchGroup,
Paged,
} from "../types";
import { toMatchGroup, toRawMatchExpression, type RawMatchSpec } from "./matchExpression";
-import { inlineIntegrationBody } from "./subscriptionBody";
+import { inlineSubscriptionBody } from "./subscriptionBody";
import { get, post, request } from "./request";
import { buildListQuery, SEARCHY_RULE } from "./searchQuery";
@@ -59,8 +59,8 @@ interface RawBusGateway {
const toApiGatewayAttachment = (p: RawApiGatewayPartner): ApiGatewayAttachment => ({
partnerId: p.partnerId,
partnerName: p.partnerName,
- integrationId: p.subscriptionId,
- integrationName: p.subscriptionName,
+ subscriptionId: p.subscriptionId,
+ subscriptionName: p.subscriptionName,
});
const toApiGatewayRow = (raw: RawApiGateway): ApiGatewayRow => ({
@@ -84,8 +84,8 @@ const toApiGatewayDetail = (raw: RawApiGateway): ApiGatewayDetail => ({
const toBusGatewayRoute = (r: RawBusGatewayRoute): BusGatewayRoute => ({
id: r.id,
- integrationId: r.subscriptionId,
- integrationName: r.subscriptionName ?? "",
+ subscriptionId: r.subscriptionId,
+ subscriptionName: r.subscriptionName ?? "",
partnerId: r.partnerId,
partnerName: r.partnerName,
matchExpression: toMatchGroup(r.matchExpression),
@@ -113,20 +113,20 @@ const toBusGatewayDetail = (raw: RawBusGateway): BusGatewayDetail => ({
routes: (raw.routes ?? []).map(toBusGatewayRoute),
});
-/** The attachment always points at an integration that already exists — a new one is
+/** The attachment always points at a subscription that already exists — a new one is
* created on its own page first, not inline here (unlike a bus gateway route, which
* still creates one in the same transaction — see `AddBusRouteInput`). */
-export type AttachPartnerInput = { partnerId: number; integrationId: number };
+export type AttachPartnerInput = { partnerId: number; subscriptionId: number };
/**
- * A route points at an integration that already exists, or defines one. Exactly one,
+ * A route points at a subscription that already exists, or defines one. Exactly one,
* which the endpoint enforces — the union makes that unrepresentable rather than
* merely wrong.
*/
export type AddBusRouteInput = {
partnerId: number | null;
matchExpression: MatchGroup | null;
-} & ({ integrationId: number } | { newIntegration: InlineIntegrationDraft });
+} & ({ subscriptionId: number } | { newSubscription: InlineSubscriptionDraft });
export const gatewayMethods = {
// ——— API gateways ———
@@ -193,17 +193,17 @@ export const gatewayMethods = {
async attachGatewayPartner(id: number, input: AttachPartnerInput): Promise {
await post(`/apigateways/${id}/addpartner`, {
partnerId: input.partnerId,
- subscriptionId: input.integrationId,
+ subscriptionId: input.subscriptionId,
});
},
- async updateGatewayAttachment(id: number, input: { partnerId: number; integrationId: number }): Promise {
+ async updateGatewayAttachment(id: number, input: { partnerId: number; subscriptionId: number }): Promise {
// Not a plain POST to updatepartner: ApiGatewayPartner's PK is the composite
// (gatewayId, partnerId, subscriptionId), and the backend's UpdatePartner
// handler tries to mutate subscriptionId in place on a tracked entity — EF
// Core rejects changes to a key column. Remove-then-add sidesteps it.
await post(`/apigateways/${id}/removepartner`, { partnerId: input.partnerId });
- await post(`/apigateways/${id}/addpartner`, { partnerId: input.partnerId, subscriptionId: input.integrationId });
+ await post(`/apigateways/${id}/addpartner`, { partnerId: input.partnerId, subscriptionId: input.subscriptionId });
},
async removeGatewayAttachment(id: number, partnerId: number): Promise {
@@ -284,11 +284,13 @@ export const gatewayMethods = {
async addBusRoute(id: number, input: AddBusRouteInput): Promise {
await post(`/busgateways/${id}/addroute`, {
- // Exactly one of the two, which is what the endpoint enforces. An integration
+ // Exactly one of the two, which is what the endpoint enforces. A subscription
// defined here is created in the same transaction as the route.
- ...("newIntegration" in input
- ? { newIntegration: inlineIntegrationBody(input.newIntegration) }
- : { subscriptionId: input.integrationId }),
+ // `newIntegration` is the wire name: BusGatewayRouteCreate.NewIntegration still
+ // carries the old wording, so the key sent here cannot follow this UI's rename.
+ ...("newSubscription" in input
+ ? { newIntegration: inlineSubscriptionBody(input.newSubscription) }
+ : { subscriptionId: input.subscriptionId }),
partnerId: input.partnerId,
matchExpression: toRawMatchExpression(input.matchExpression),
});
@@ -297,11 +299,11 @@ export const gatewayMethods = {
async updateBusRoute(
id: number,
routeId: number,
- input: { integrationId: number; partnerId: number | null; matchExpression: MatchGroup | null },
+ input: { subscriptionId: number; partnerId: number | null; matchExpression: MatchGroup | null },
): Promise {
await post(`/busgateways/${id}/updateroute`, {
routeId,
- subscriptionId: input.integrationId,
+ subscriptionId: input.subscriptionId,
partnerId: input.partnerId,
matchExpression: toRawMatchExpression(input.matchExpression),
});
diff --git a/SW.Bitween.Web/ClientApp/src/api/http/globalValues.ts b/SW.Bitween.Web/ClientApp/src/api/http/globalValues.ts
index 38ec544c..51268ee4 100644
--- a/SW.Bitween.Web/ClientApp/src/api/http/globalValues.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/http/globalValues.ts
@@ -1,5 +1,5 @@
import type { ApiClient } from "../client";
-import type { GlobalValuesSet, GlobalValuesSetDetail, GlobalValuesSetRow, IntegrationType, ValueSetUsage } from "../types";
+import type { GlobalValuesSet, GlobalValuesSetDetail, GlobalValuesSetRow, SubscriptionType, ValueSetUsage } from "../types";
import { referencesGlobal, scanReferenceTokens } from "./references";
import { get, getEnrichment, post } from "./request";
@@ -26,7 +26,7 @@ interface RawSubscriptionForUsage {
validatorProperties: RawKeyAndValue[] | null;
}
-const SUB_TYPE_BY_NUM: Record = {
+const SUB_TYPE_BY_NUM: Record = {
1: "Internal",
2: "ApiCall",
4: "Receiving",
@@ -34,7 +34,7 @@ const SUB_TYPE_BY_NUM: Record = {
16: "GatewayApiCall",
32: "BusGateway",
};
-const INTEGRATION_TYPES: IntegrationType[] = [
+const SUBSCRIPTION_TYPES: SubscriptionType[] = [
"Receiving",
"GatewayApiCall",
"BusGateway",
@@ -43,9 +43,9 @@ const INTEGRATION_TYPES: IntegrationType[] = [
"Aggregation",
];
/** Enums may arrive as the numeric value or the name in any case. */
-const toIntegrationType = (t: number | string): IntegrationType => {
+const toSubscriptionType = (t: number | string): SubscriptionType => {
if (typeof t === "number") return SUB_TYPE_BY_NUM[t] ?? "Internal";
- return INTEGRATION_TYPES.find((k) => k.toLowerCase() === t.toLowerCase()) ?? "Internal";
+ return SUBSCRIPTION_TYPES.find((k) => k.toLowerCase() === t.toLowerCase()) ?? "Internal";
};
// GlobalAdapterValuesSet has no CreatedOn column on the backend.
@@ -72,7 +72,7 @@ function globalKeysReferencedBy(sub: RawSubscriptionForUsage, setId: string): st
export const globalValuesMethods = {
- // No usage scan here: the list page answers "used by" from the integrations
+ // No usage scan here: the list page answers "used by" from the subscriptions
// cache it already holds, so a second full /subscriptions fetch would be waste.
async listValueSets(): Promise {
const res = await get>("/globaladaptervaluessets");
@@ -86,7 +86,7 @@ export const globalValuesMethods = {
]);
const usedBy: ValueSetUsage[] = subs
.map((s) => ({
- integrationSetup: { id: s.id, name: s.name, type: toIntegrationType(s.type) },
+ subscriptionSetup: { id: s.id, name: s.name, type: toSubscriptionType(s.type) },
keys: globalKeysReferencedBy(s, id),
}))
.filter((u) => u.keys.length > 0);
diff --git a/SW.Bitween.Web/ClientApp/src/api/http/httpClient.ts b/SW.Bitween.Web/ClientApp/src/api/http/httpClient.ts
index 9b3dd3e7..8efb95e5 100644
--- a/SW.Bitween.Web/ClientApp/src/api/http/httpClient.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/http/httpClient.ts
@@ -6,7 +6,7 @@ import { documentMethods } from "./documents";
import { exchangeMethods } from "./exchanges";
import { gatewayMethods } from "./gateways";
import { globalValuesMethods } from "./globalValues";
-import { integrationMethods } from "./integrations";
+import { subscriptionMethods } from "./subscriptions";
import { mapperMethods } from "./mappers";
import { notifierMethods } from "./notifiers";
import { partnerMethods } from "./partners";
@@ -30,7 +30,7 @@ const wired: Partial = {
...globalValuesMethods,
...workGroupMethods,
...retryPolicyMethods,
- ...integrationMethods,
+ ...subscriptionMethods,
...adapterMethods,
...gatewayMethods,
...exchangeMethods,
diff --git a/SW.Bitween.Web/ClientApp/src/api/http/notifiers.ts b/SW.Bitween.Web/ClientApp/src/api/http/notifiers.ts
index b55a54fb..91e749db 100644
--- a/SW.Bitween.Web/ClientApp/src/api/http/notifiers.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/http/notifiers.ts
@@ -56,7 +56,7 @@ const toNotifier = (r: RawNotifier): Notifier => ({
onSuccess: r.runOnSuccessfulResult,
channelId: r.handlerId ?? "",
channelProperties: toRecord(r.handlerProperties),
- integrationIds: (r.runOnSubscriptions ?? []).map((s) => s.id),
+ subscriptionIds: (r.runOnSubscriptions ?? []).map((s) => s.id),
createdOn: "",
});
@@ -98,7 +98,7 @@ export const notifierMethods = {
onSuccess: !!r.runOnSuccessfulResult,
channelId: r.handlerId ?? "",
channelProperties: {},
- integrationIds: r.runOnSubscriptions ?? [],
+ subscriptionIds: r.runOnSubscriptions ?? [],
createdOn: "",
})),
};
@@ -117,7 +117,7 @@ export const notifierMethods = {
onSuccess: false,
channelId: "",
channelProperties: {},
- integrationIds: [],
+ subscriptionIds: [],
createdOn: "",
};
},
@@ -131,7 +131,7 @@ export const notifierMethods = {
handlerId: changes.channelId,
inactive: !changes.enabled,
handlerProperties: toKvArray(changes.channelProperties),
- runOnSubscriptions: changes.integrationIds.map((subscriptionId) => ({ id: subscriptionId })),
+ runOnSubscriptions: changes.subscriptionIds.map((subscriptionId) => ({ id: subscriptionId })),
});
return { id, createdOn: "", ...changes };
},
diff --git a/SW.Bitween.Web/ClientApp/src/api/http/retryPolicies.ts b/SW.Bitween.Web/ClientApp/src/api/http/retryPolicies.ts
index ade1a22e..9d488724 100644
--- a/SW.Bitween.Web/ClientApp/src/api/http/retryPolicies.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/http/retryPolicies.ts
@@ -1,7 +1,7 @@
import type { ApiClient } from "../client";
import {
ApiRequestError,
- type IntegrationType,
+ type SubscriptionType,
type RetryAlertConfig,
type RetryAlertLevel,
type RetryAttempts,
@@ -39,7 +39,7 @@ interface RawSubscriptionRef {
type: number | string;
}
-const SUB_TYPE_BY_NUM: Record = {
+const SUB_TYPE_BY_NUM: Record = {
1: "Internal",
2: "ApiCall",
4: "Receiving",
@@ -47,7 +47,7 @@ const SUB_TYPE_BY_NUM: Record = {
16: "GatewayApiCall",
32: "BusGateway",
};
-const INTEGRATION_TYPES: IntegrationType[] = [
+const SUBSCRIPTION_TYPES: SubscriptionType[] = [
"Receiving",
"GatewayApiCall",
"BusGateway",
@@ -56,9 +56,9 @@ const INTEGRATION_TYPES: IntegrationType[] = [
"Aggregation",
];
/** Enums may arrive as the numeric value or the name in any case. */
-const toIntegrationType = (t: number | string): IntegrationType => {
+const toSubscriptionType = (t: number | string): SubscriptionType => {
if (typeof t === "number") return SUB_TYPE_BY_NUM[t] ?? "Internal";
- return INTEGRATION_TYPES.find((k) => k.toLowerCase() === t.toLowerCase()) ?? "Internal";
+ return SUBSCRIPTION_TYPES.find((k) => k.toLowerCase() === t.toLowerCase()) ?? "Internal";
};
async function fetchSubscriptionsByRetryPolicy(retryPolicyId: number): Promise {
@@ -167,7 +167,7 @@ async function fetchDetail(id: number): Promise {
createdOn: "",
alertHandlerId: r.alertHandlerId ?? null,
alertHandlerProperties: r.alertHandlerProperties ?? {},
- integrations: subs.map((s) => ({ id: s.id, name: s.name, type: toIntegrationType(s.type) })),
+ subscriptions: subs.map((s) => ({ id: s.id, name: s.name, type: toSubscriptionType(s.type) })),
};
}
@@ -193,8 +193,8 @@ interface RawUsageRow {
}
const toUsageRow = (r: RawUsageRow): RetryUsageRow => ({
- integrationId: r.subscriptionId,
- integrationName: r.subscriptionName,
+ subscriptionId: r.subscriptionId,
+ subscriptionName: r.subscriptionName,
groupId: r.groupId,
groupName: r.groupName,
used: r.attemptsUsed,
@@ -310,18 +310,18 @@ export const retryPolicyMethods = {
return (rows ?? []).map(toUsageRow);
},
- async getIntegrationRetryUsage(integrationId: number): Promise {
- const rows = await post(`/subscriptions/${integrationId}/retryusage`, {});
+ async getSubscriptionRetryUsage(subscriptionId: number): Promise {
+ const rows = await post(`/subscriptions/${subscriptionId}/retryusage`, {});
return (rows ?? []).map(toUsageRow);
},
async getRetryAttempts(
policyId: number,
- pair: { integrationId: number; groupId: string },
+ pair: { subscriptionId: number; groupId: string },
): Promise {
const res = await post<{ total: number; attempts: RawAttempt[] }>(
`/retrypolicies/${policyId}/attempts`,
- { subscriptionId: pair.integrationId, groupId: pair.groupId },
+ { subscriptionId: pair.subscriptionId, groupId: pair.groupId },
);
return {
total: res?.total ?? 0,
@@ -336,23 +336,23 @@ export const retryPolicyMethods = {
};
},
- async resetRetryUsage(policyId: number, pair?: { integrationId?: number; groupId?: string }): Promise {
+ async resetRetryUsage(policyId: number, pair?: { subscriptionId?: number; groupId?: string }): Promise {
await post(`/retrypolicies/${policyId}/resetusage`, {
- subscriptionId: pair?.integrationId ?? null,
+ subscriptionId: pair?.subscriptionId ?? null,
groupId: pair?.groupId ?? null,
});
},
- async resetIntegrationRetryUsage(integrationId: number, groupId?: string): Promise {
- await post(`/subscriptions/${integrationId}/resetretryusage`, { groupId: groupId ?? null });
+ async resetSubscriptionRetryUsage(subscriptionId: number, groupId?: string): Promise {
+ await post(`/subscriptions/${subscriptionId}/resetretryusage`, { groupId: groupId ?? null });
},
async saveRetryAlertOverride(
policyId: number,
- input: { integrationId: number; groupId: string } & RetryAlertConfig,
+ input: { subscriptionId: number; groupId: string } & RetryAlertConfig,
): Promise {
await post(`/retrypolicies/${policyId}/savealertoverride`, {
- subscriptionId: input.integrationId,
+ subscriptionId: input.subscriptionId,
groupId: input.groupId,
alertMode: input.alertMode,
alertHandlerId: input.alertHandlerId,
diff --git a/SW.Bitween.Web/ClientApp/src/api/http/subscriptionBody.ts b/SW.Bitween.Web/ClientApp/src/api/http/subscriptionBody.ts
index a8feb501..fd121823 100644
--- a/SW.Bitween.Web/ClientApp/src/api/http/subscriptionBody.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/http/subscriptionBody.ts
@@ -1,12 +1,12 @@
-import type { InlineIntegrationDraft, Schedule } from "../types";
+import type { InlineSubscriptionDraft, Schedule } from "../types";
import { toRawMatchExpression } from "./matchExpression";
/**
- * The subscription wire shape, in the one place both the integration endpoints and
+ * The subscription wire shape, in the one place both the subscription endpoints and
* the gateway endpoints can reach.
*
- * It lives here rather than in `integrations.ts` because `gateways.ts` needs it too,
- * and `integrations.ts` already imports `gateways.ts` — putting it there would make
+ * It lives here rather than in `subscriptions.ts` because `gateways.ts` needs it too,
+ * and `subscriptions.ts` already imports `gateways.ts` — putting it there would make
* that cycle mutual.
*/
@@ -36,11 +36,11 @@ export const toRawSchedules = (schedules: Schedule[]): RawSchedule[] =>
}));
/**
- * An integration defined on a gateway's canvas, in the shape the gateway endpoints
+ * A subscription defined on a gateway's canvas, in the shape the gateway endpoints
* take. No `documentId`: a bus gateway imposes its own, and the API-gateway caller
* adds the one its picker chose.
*/
-export const inlineIntegrationBody = (d: InlineIntegrationDraft) => ({
+export const inlineSubscriptionBody = (d: InlineSubscriptionDraft) => ({
name: d.name.trim(),
inactive: !d.enabled,
workGroupId: d.workGroupId,
@@ -56,6 +56,6 @@ export const inlineIntegrationBody = (d: InlineIntegrationDraft) => ({
handlerProperties: toKvArray(d.handlerProperties),
matchExpression: toRawMatchExpression(d.matchExpression),
schedules: toRawSchedules(d.schedules),
- responseSubscriptionId: d.responseIntegrationId,
+ responseSubscriptionId: d.responseSubscriptionId,
responseMessageTypeName: d.responseMessageTypeName,
});
diff --git a/SW.Bitween.Web/ClientApp/src/api/http/integrations.ts b/SW.Bitween.Web/ClientApp/src/api/http/subscriptions.ts
similarity index 84%
rename from SW.Bitween.Web/ClientApp/src/api/http/integrations.ts
rename to SW.Bitween.Web/ClientApp/src/api/http/subscriptions.ts
index 910ce616..0266ac9e 100644
--- a/SW.Bitween.Web/ClientApp/src/api/http/integrations.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/http/subscriptions.ts
@@ -1,13 +1,13 @@
import type { ApiClient } from "../client";
import {
ApiRequestError,
- type Integration,
- type IntegrationDetail,
- type IntegrationInfo,
- type IntegrationLastRun,
- type IntegrationRow,
- type IntegrationRun,
- type IntegrationType,
+ type Subscription,
+ type SubscriptionDetail,
+ type SubscriptionInfo,
+ type SubscriptionLastRun,
+ type SubscriptionRow,
+ type SubscriptionRun,
+ type SubscriptionType,
type InformationTypeRow,
type Paged,
type PartnerRow,
@@ -96,7 +96,7 @@ interface RawSubscription {
aggregationTarget?: string;
}
-const SUB_TYPE_BY_NUM: Record = {
+const SUB_TYPE_BY_NUM: Record = {
1: "Internal",
2: "ApiCall",
4: "Receiving",
@@ -104,7 +104,7 @@ const SUB_TYPE_BY_NUM: Record = {
16: "GatewayApiCall",
32: "BusGateway",
};
-const INTEGRATION_TYPES: IntegrationType[] = [
+const SUBSCRIPTION_TYPES: SubscriptionType[] = [
"Receiving",
"GatewayApiCall",
"BusGateway",
@@ -113,10 +113,10 @@ const INTEGRATION_TYPES: IntegrationType[] = [
"Aggregation",
];
/** Enums serialize as their C# member name string, but guard the numeric case too. */
-const toIntegrationType = (t: number | string): IntegrationType =>
+const toSubscriptionType = (t: number | string): SubscriptionType =>
typeof t === "number"
? (SUB_TYPE_BY_NUM[t] ?? "Internal")
- : (INTEGRATION_TYPES.find((k) => k.toLowerCase() === t.toLowerCase()) ?? "Internal");
+ : (SUBSCRIPTION_TYPES.find((k) => k.toLowerCase() === t.toLowerCase()) ?? "Internal");
// Empty-valued properties are dropped: an adapter property with no value means
// "not set", and keeping it would make a freshly-cleared field compare unequal to
@@ -133,11 +133,11 @@ const toSchedules = (raw: RawSchedule[] | null): Schedule[] =>
backwards: s.backwards,
}));
-function toIntegration(raw: RawSubscription, idOverride?: number): Integration {
+function toSubscription(raw: RawSubscription, idOverride?: number): Subscription {
return {
id: raw.id ?? idOverride!,
name: raw.name,
- type: toIntegrationType(raw.type),
+ type: toSubscriptionType(raw.type),
informationTypeId: raw.documentId,
partnerId: raw.partnerId,
enabled: !raw.inactive,
@@ -154,7 +154,7 @@ function toIntegration(raw: RawSubscription, idOverride?: number): Integration {
handlerProperties: toRecord(raw.handlerProperties),
matchExpression: toMatchGroup(raw.matchExpression),
schedules: toSchedules(raw.schedules),
- responseIntegrationId: raw.responseSubscriptionId ?? null,
+ responseSubscriptionId: raw.responseSubscriptionId ?? null,
responseMessageTypeName: raw.responseMessageTypeName ?? null,
aggregationForId: raw.aggregationForId ?? null,
isRunning: raw.isRunning ?? false,
@@ -168,7 +168,7 @@ function toIntegration(raw: RawSubscription, idOverride?: number): Integration {
async function fetchRaw(id: number): Promise {
const raw = await get(`/subscriptions/${id}`);
- if (!raw) throw new ApiRequestError("NOT_FOUND", "This integration no longer exists.");
+ if (!raw) throw new ApiRequestError("NOT_FOUND", "This subscription no longer exists.");
return raw;
}
@@ -179,7 +179,7 @@ async function fetchAllRaw(): Promise {
type UpdatableFields = Partial<
Pick<
- Integration,
+ Subscription,
| "name"
| "enabled"
| "workGroupId"
@@ -194,7 +194,7 @@ type UpdatableFields = Partial<
| "handlerProperties"
| "matchExpression"
| "schedules"
- | "responseIntegrationId"
+ | "responseSubscriptionId"
| "responseMessageTypeName"
>
>;
@@ -236,7 +236,7 @@ async function applyChanges(id: number, current: RawSubscription, changes: Updat
changes.matchExpression !== undefined ? toRawMatchExpression(changes.matchExpression) : current.matchExpression,
schedules: changes.schedules !== undefined ? toRawSchedules(changes.schedules) : (current.schedules ?? []),
responseSubscriptionId:
- changes.responseIntegrationId !== undefined ? changes.responseIntegrationId : current.responseSubscriptionId,
+ changes.responseSubscriptionId !== undefined ? changes.responseSubscriptionId : current.responseSubscriptionId,
responseMessageTypeName:
changes.responseMessageTypeName !== undefined ? changes.responseMessageTypeName : current.responseMessageTypeName,
temporary: current.temporary,
@@ -249,12 +249,12 @@ async function applyChanges(id: number, current: RawSubscription, changes: Updat
});
}
-function toIntegrationRow(
+function toSubscriptionRow(
raw: RawSubscription,
infoTypeById: Map,
partnerById: Map,
-): IntegrationRow {
- const type = toIntegrationType(raw.type);
+): SubscriptionRow {
+ const type = toSubscriptionType(raw.type);
const infoType = infoTypeById.get(raw.documentId);
const partner = raw.partnerId !== null ? partnerById.get(raw.partnerId) : undefined;
const schedules = toSchedules(raw.schedules);
@@ -284,20 +284,20 @@ function toIntegrationRow(
};
}
-export const integrationMethods = {
- async listIntegrations(): Promise {
+export const subscriptionMethods = {
+ async listSubscriptions(): Promise {
const rows = await fetchAllRaw();
return rows.map((raw) => ({
id: raw.id!,
name: raw.name,
- type: toIntegrationType(raw.type),
+ type: toSubscriptionType(raw.type),
partnerIds: raw.partnerId !== null ? [raw.partnerId] : [],
informationTypeId: raw.documentId,
workGroupId: raw.workGroupId ?? null,
retryPolicyId: raw.retryPolicyId ?? null,
handlerId: raw.handlerId ?? null,
responseMessageTypeName: raw.responseMessageTypeName ?? null,
- responseIntegrationId: raw.responseSubscriptionId ?? null,
+ responseSubscriptionId: raw.responseSubscriptionId ?? null,
// No backend endpoint indexes reference tokens, but the search rows carry
// every adapter property, so the scan costs nothing extra here.
...scanReferenceTokens(
@@ -311,7 +311,7 @@ export const integrationMethods = {
}));
},
- async listIntegrationRows(): Promise {
+ async listSubscriptionRows(): Promise {
const [rows, infoTypes, partners] = await Promise.all([
fetchAllRaw(),
documentMethods.listInformationTypes(),
@@ -319,18 +319,18 @@ export const integrationMethods = {
]);
const infoTypeById = new Map(infoTypes.map((t) => [t.id, t]));
const partnerById = new Map(partners.map((p) => [p.id, p]));
- return rows.map((raw) => toIntegrationRow(raw, infoTypeById, partnerById));
+ return rows.map((raw) => toSubscriptionRow(raw, infoTypeById, partnerById));
},
- async searchIntegrationRows(query: {
+ async searchSubscriptionRows(query: {
search: string;
- type: IntegrationType | null;
+ type: SubscriptionType | null;
informationTypeId?: number | null;
partnerId?: number | null;
inactive?: boolean | null;
offset: number;
limit: number;
- }): Promise> {
+ }): Promise> {
const qs = buildListQuery({
filters: [
["Name", SEARCHY_RULE.contains, query.search.trim()],
@@ -352,25 +352,25 @@ export const integrationMethods = {
const partnerById = new Map(partners.map((p) => [p.id, p]));
return {
total: res.totalCount,
- result: (res.result ?? []).map((raw) => toIntegrationRow(raw, infoTypeById, partnerById)),
+ result: (res.result ?? []).map((raw) => toSubscriptionRow(raw, infoTypeById, partnerById)),
};
},
- async getIntegration(id: number): Promise {
+ async getSubscription(id: number): Promise {
const [raw, apiGateways, busGateways, recentExchanges] = await Promise.all([
fetchRaw(id),
gatewayMethods.listApiGateways(),
gatewayMethods.listBusGateways(),
- exchangeMethods.searchExchanges({ integrationId: id, offset: 0, limit: 8 }),
+ exchangeMethods.searchExchanges({ subscriptionId: id, offset: 0, limit: 8 }),
]);
const infoType = await documentMethods.getInformationType(raw.documentId).catch(() => null);
return {
- ...toIntegration(raw, id),
+ ...toSubscription(raw, id),
informationTypeCode: infoType?.code ?? infoType?.name ?? "",
informationTypeName: infoType?.name ?? "",
apiGatewayAttachments: apiGateways.flatMap((g) =>
g.attachments
- .filter((a) => a.integrationId === id)
+ .filter((a) => a.subscriptionId === id)
.map((a) => ({
gatewayId: g.id,
gatewayName: g.name,
@@ -381,7 +381,7 @@ export const integrationMethods = {
),
busGatewayRoutes: busGateways.flatMap((g) =>
g.routes
- .filter((r) => r.integrationId === id)
+ .filter((r) => r.subscriptionId === id)
.map((r) => ({ gatewayId: g.id, gatewayName: g.name, partnerId: r.partnerId, partnerName: r.partnerName })),
),
recentExchanges: recentExchanges.result.map((x) => ({
@@ -399,8 +399,8 @@ export const integrationMethods = {
};
},
- async createIntegration(input: {
- type: IntegrationType;
+ async createSubscription(input: {
+ type: SubscriptionType;
name: string;
informationTypeId: number;
/** Required by the types that carry their own partner — Internal and ApiCall. */
@@ -415,10 +415,10 @@ export const integrationMethods = {
handlerProperties?: Record;
schedules?: Schedule[];
retryPolicyId?: number | null;
- responseIntegrationId?: number | null;
+ responseSubscriptionId?: number | null;
responseMessageTypeName?: string | null;
enabled?: boolean;
- }): Promise {
+ }): Promise {
// One call, one transaction. This used to be a POST followed by a PATCH,
// because create accepted only the name/type/document — and since the POST
// committed on its own, a rejected PATCH left an empty subscription behind.
@@ -443,35 +443,35 @@ export const integrationMethods = {
schedules: input.schedules?.length ? toRawSchedules(input.schedules) : undefined,
retryPolicyId: input.retryPolicyId ?? null,
customRetryPolicy: null,
- responseSubscriptionId: input.responseIntegrationId ?? null,
+ responseSubscriptionId: input.responseSubscriptionId ?? null,
responseMessageTypeName: input.responseMessageTypeName ?? null,
inactive: !(input.enabled ?? false),
});
- return toIntegration(await fetchRaw(id), id);
+ return toSubscription(await fetchRaw(id), id);
},
- async updateIntegration(id: number, changes: UpdatableFields): Promise {
+ async updateSubscription(id: number, changes: UpdatableFields): Promise {
const current = await fetchRaw(id);
await applyChanges(id, current, changes);
- return toIntegration(await fetchRaw(id), id);
+ return toSubscription(await fetchRaw(id), id);
},
- async deleteIntegration(id: number): Promise {
+ async deleteSubscription(id: number): Promise {
await request(`/subscriptions/${id}`, { method: "DELETE" });
},
- async pauseIntegration(id: number): Promise {
+ async pauseSubscription(id: number): Promise {
await post(`/subscriptions/${id}/pause`, {});
- return toIntegration(await fetchRaw(id), id);
+ return toSubscription(await fetchRaw(id), id);
},
- async receiveNow(id: number): Promise {
+ async receiveNow(id: number): Promise {
await post(`/subscriptions/${id}/receivenow`, {});
- return toIntegration(await fetchRaw(id), id);
+ return toSubscription(await fetchRaw(id), id);
},
- listIntegrationRuns(id: number, limit = 20): Promise {
- return get(`/subscriptions/runs?subscriptionId=${id}&limit=${limit}`);
+ listSubscriptionRuns(id: number, limit = 20): Promise {
+ return get(`/subscriptions/runs?subscriptionId=${id}&limit=${limit}`);
},
async searchReceiveAttempts(
@@ -502,19 +502,13 @@ export const integrationMethods = {
};
},
- async listLastRuns(): Promise {
- const rows =
- await get<(Omit & { subscriptionId: number })[]>(
- "/subscriptions/lastruns",
- );
- return rows.map(({ subscriptionId, ...run }) => ({ ...run, integrationId: subscriptionId }));
+ // Both of these used to rename the wire's `subscriptionId` onto an `integrationId` field;
+ // now that the field carries the wire's own name, the rows come back already shaped.
+ async listLastRuns(): Promise {
+ return get("/subscriptions/lastruns");
},
async listScheduleHealth(): Promise {
- const rows =
- await get<(Omit & { subscriptionId: number })[]>(
- "/subscriptions/schedulehealth",
- );
- return rows.map(({ subscriptionId, ...health }) => ({ ...health, integrationId: subscriptionId }));
+ return get("/subscriptions/schedulehealth");
},
} satisfies Partial;
diff --git a/SW.Bitween.Web/ClientApp/src/api/http/workGroups.ts b/SW.Bitween.Web/ClientApp/src/api/http/workGroups.ts
index 51b9a303..0e39494d 100644
--- a/SW.Bitween.Web/ClientApp/src/api/http/workGroups.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/http/workGroups.ts
@@ -1,7 +1,7 @@
import type { ApiClient } from "../client";
import {
ApiRequestError,
- type IntegrationType,
+ type SubscriptionType,
type WorkGroup,
type WorkGroupDetail,
type WorkGroupRow,
@@ -26,7 +26,7 @@ interface RawSubscriptionRef {
type: number | string;
}
-const SUB_TYPE_BY_NUM: Record = {
+const SUB_TYPE_BY_NUM: Record = {
1: "Internal",
2: "ApiCall",
4: "Receiving",
@@ -34,7 +34,7 @@ const SUB_TYPE_BY_NUM: Record = {
16: "GatewayApiCall",
32: "BusGateway",
};
-const INTEGRATION_TYPES: IntegrationType[] = [
+const SUBSCRIPTION_TYPES: SubscriptionType[] = [
"Receiving",
"GatewayApiCall",
"BusGateway",
@@ -43,9 +43,9 @@ const INTEGRATION_TYPES: IntegrationType[] = [
"Aggregation",
];
/** Enums may arrive as the numeric value or the name in any case. */
-const toIntegrationType = (t: number | string): IntegrationType => {
+const toSubscriptionType = (t: number | string): SubscriptionType => {
if (typeof t === "number") return SUB_TYPE_BY_NUM[t] ?? "Internal";
- return INTEGRATION_TYPES.find((k) => k.toLowerCase() === t.toLowerCase()) ?? "Internal";
+ return SUBSCRIPTION_TYPES.find((k) => k.toLowerCase() === t.toLowerCase()) ?? "Internal";
};
async function fetchSubscriptionsByWorkGroup(workGroupId: number): Promise {
@@ -141,7 +141,7 @@ export const workGroupMethods = {
if (!w) throw new ApiRequestError("NOT_FOUND", "This work group no longer exists.");
return {
...toWorkGroup(w),
- integrations: subs.map((s) => ({ id: s.id, name: s.name, type: toIntegrationType(s.type) })),
+ subscriptions: subs.map((s) => ({ id: s.id, name: s.name, type: toSubscriptionType(s.type) })),
};
},
diff --git a/SW.Bitween.Web/ClientApp/src/api/permissions.ts b/SW.Bitween.Web/ClientApp/src/api/permissions.ts
index a1cb5923..dec228ae 100644
--- a/SW.Bitween.Web/ClientApp/src/api/permissions.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/permissions.ts
@@ -40,7 +40,7 @@ export const groupsIn = (areas: PermissionArea[]): string[] => [
...new Set(areas.map((area) => area.group)),
];
-/** "Integrations · Edit" for a key, falling back to the raw key for anything unrecognised. */
+/** "Subscriptions · Edit" for a key, falling back to the raw key for anything unrecognised. */
export const labelIn = (areas: PermissionArea[], key: PermissionKey): string => {
const [areaId, actionId] = key.split(".");
const area = areas.find((a) => a.id === areaId);
diff --git a/SW.Bitween.Web/ClientApp/src/api/types.ts b/SW.Bitween.Web/ClientApp/src/api/types.ts
index 4e35d18c..d748d454 100644
--- a/SW.Bitween.Web/ClientApp/src/api/types.ts
+++ b/SW.Bitween.Web/ClientApp/src/api/types.ts
@@ -91,10 +91,10 @@ export class NotWiredError extends Error {
// ——— Configuration entities (sub-phase 2) ———
/** Lightweight references for "used by" panels. */
-export interface IntegrationSetupRef {
+export interface SubscriptionSetupRef {
id: number;
name: string;
- type: IntegrationType;
+ type: SubscriptionType;
}
export interface ApiGatewayAttachmentRef {
gatewayId: number;
@@ -125,18 +125,18 @@ export interface ExchangeRef {
}
/**
- * Lightweight summary of every integration, cached client-side so pages
+ * Lightweight summary of every subscription, cached client-side so pages
* can answer "who uses this property/value/policy?" without new requests.
* Derived server-side by scanning adapter property values for tokens.
*/
-export interface IntegrationInfo {
+export interface SubscriptionInfo {
id: number;
name: string;
- type: IntegrationType;
+ type: SubscriptionType;
/**
- * The integration's OWN partner, which only the legacy types carry. Partners
+ * The subscription's OWN partner, which only the legacy types carry. Partners
* linked through a gateway attachment or a bus route are NOT here — the list
- * endpoint doesn't know about them. Use `usePartnerIntegrations()` when you
+ * endpoint doesn't know about them. Use `usePartnerSubscriptions()` when you
* need the full picture.
*/
partnerIds: number[];
@@ -151,8 +151,8 @@ export interface IntegrationInfo {
handlerId: string | null;
/** The bus message its delivery response is published as, if any. */
responseMessageTypeName: string | null;
- /** The integration its delivery response is handed straight to, if any. */
- responseIntegrationId: number | null;
+ /** The subscription its delivery response is handed straight to, if any. */
+ responseSubscriptionId: number | null;
/**
* Reference tokens found in its adapter properties. Both are matched
* case-insensitively, as the backend resolver does — compare them with
@@ -219,7 +219,7 @@ export interface InformationTypeRow extends InformationType {
usedByCount: number;
}
export interface InformationTypeDetail extends InformationType {
- integrationSetups: IntegrationSetupRef[];
+ subscriptionSetups: SubscriptionSetupRef[];
busGateways: { gatewayId: number; gatewayName: string }[];
trail: TrailEntry[];
recentExchanges: ExchangeRef[];
@@ -235,7 +235,7 @@ export interface GlobalValuesSet {
/** Alias the ported mapper code types its global-set props with. */
export type GlobalValuesSetRow = GlobalValuesSet;
export interface ValueSetUsage {
- integrationSetup: IntegrationSetupRef;
+ subscriptionSetup: SubscriptionSetupRef;
keys: string[];
}
export interface GlobalValuesSetDetail extends GlobalValuesSet {
@@ -260,7 +260,7 @@ export type RetryDelay =
/**
* Whether a level of the alert hierarchy names its own destination or defers upward.
*
- * Resolved most-specific-first per integration and group: the pair's own override, then
+ * Resolved most-specific-first per subscription and group: the pair's own override, then
* the group, then the policy. A level that sends **replaces** the one above rather than
* merging with it, so whichever level wins has to carry the handler and every property
* it needs.
@@ -289,7 +289,7 @@ export interface RetryGroup {
action: "Allow" | "Block";
budget?: { maxAttemptsPerError: number; maxAttemptsTotal: number; delay: RetryDelay };
notes?: string;
- /** Where this group's budget-exhausted alert goes, for every integration using the policy. */
+ /** Where this group's budget-exhausted alert goes, for every subscription using the policy. */
alertMode: RetryAlertMode;
alertHandlerId: string | null;
alertHandlerProperties: Record;
@@ -313,7 +313,7 @@ export interface RetryPolicyListRow {
usedByCount: number;
}
export interface RetryPolicyDetail extends RetryPolicy {
- integrations: IntegrationSetupRef[];
+ subscriptions: SubscriptionSetupRef[];
}
/**
@@ -332,22 +332,22 @@ export interface RetryAlertOutcome {
}
/**
- * The whole state of one integration-and-group pair: how much of the group's budget that
- * integration has spent, and where the pair's budget-exhausted alert would go.
+ * The whole state of one subscription-and-group pair: how much of the group's budget that
+ * subscription has spent, and where the pair's budget-exhausted alert would go.
*
- * Budgets are counted per pair — a shared policy gives every integration its own separate total
+ * Budgets are counted per pair — a shared policy gives every subscription its own separate total
* — so there is no such thing as "this policy's usage". Any single figure on a policy or a group
* would be an aggregate matching nothing anyone can act on, which is why the pair is also what
* resetting and overriding both address.
*/
export interface RetryUsageRow {
- integrationId: number;
- integrationName: string;
+ subscriptionId: number;
+ subscriptionName: string;
groupId: string;
groupName: string;
used: number;
total: number;
- /** Spent out: this integration gets no further automatic retries from this group. */
+ /** Spent out: this subscription gets no further automatic retries from this group. */
exhausted: boolean;
/** Null when the pair has never failed — also how you know there is no counter to reset. */
lastAttemptOn: string | null;
@@ -377,7 +377,7 @@ export interface RetryAttempt {
export interface RetryAttempts {
/**
- * Every failure this group has caught for this integration. Failures outlive the counter,
+ * Every failure this group has caught for this subscription. Failures outlive the counter,
* which is reset, so this is not the counter's value.
*/
total: number;
@@ -405,8 +405,8 @@ export interface Notifier {
onSuccess: boolean;
channelId: string;
channelProperties: Record;
- /** Integrations this notifier watches; empty = it never fires. */
- integrationIds: number[];
+ /** Subscriptions this notifier watches; empty = it never fires. */
+ subscriptionIds: number[];
createdOn: string;
}
@@ -422,18 +422,18 @@ export interface NotifierDetail extends Notifier {
recentNotifications: NotificationEntry[];
}
-// ——— Integrations (subscriptions) ———
+// ——— Subscriptions (subscriptions) ———
/**
* Backend Subscription.Type. Aggregation exists in data but is deferred in
* this UI; Internal and ApiCall are legacy — shown and editable, never created.
*/
/**
- * The editable fields of an integration being defined inline, while whatever points
+ * The editable fields of a subscription being defined inline, while whatever points
* at it is being made. Mirrors the studio's own draft — deliberately, so the canvas
* can hand its draft straight to the client.
*/
-export interface InlineIntegrationDraft {
+export interface InlineSubscriptionDraft {
name: string;
enabled: boolean;
workGroupId: number | null;
@@ -448,11 +448,11 @@ export interface InlineIntegrationDraft {
handlerProperties: Record;
matchExpression: MatchGroup | null;
schedules: Schedule[];
- responseIntegrationId: number | null;
+ responseSubscriptionId: number | null;
responseMessageTypeName: string | null;
}
-export type IntegrationType =
+export type SubscriptionType =
| "Receiving"
| "GatewayApiCall"
| "BusGateway"
@@ -511,10 +511,10 @@ export interface Schedule {
backwards: boolean;
}
-export interface Integration {
+export interface Subscription {
id: number;
name: string;
- type: IntegrationType;
+ type: SubscriptionType;
informationTypeId: number;
/** Direct partner — legacy Internal/ApiCall (and Aggregation) only. */
partnerId: number | null;
@@ -532,12 +532,12 @@ export interface Integration {
mapperProperties: Record;
handlerId: string | null;
handlerProperties: Record;
- /** Legacy Internal only: which documents this integration picks up. */
+ /** Legacy Internal only: which documents this subscription picks up. */
matchExpression: MatchGroup | null;
/** Receiving (and Aggregation) only. */
schedules: Schedule[];
- /** Feed the handler's response into another integration. */
- responseIntegrationId: number | null;
+ /** Feed the handler's response into another subscription. */
+ responseSubscriptionId: number | null;
responseMessageTypeName: string | null;
aggregationForId: number | null;
// — health (read-only) —
@@ -549,10 +549,10 @@ export interface Integration {
createdOn: string;
}
-export interface IntegrationRow {
+export interface SubscriptionRow {
id: number;
name: string;
- type: IntegrationType;
+ type: SubscriptionType;
informationTypeId: number;
informationTypeCode: string;
partners: { id: number; name: string }[];
@@ -568,10 +568,10 @@ export interface IntegrationRow {
}
/**
- * One execution of a scheduled integration, from the scheduler's own history.
+ * One execution of a scheduled subscription, from the scheduler's own history.
* Kept for `RetentionDays` (~30) — older runs are purged, not archived here.
*/
-export interface IntegrationRun {
+export interface SubscriptionRun {
startedOn: string;
endedOn: string | null;
durationMs: number | null;
@@ -583,14 +583,14 @@ export interface IntegrationRun {
manual: boolean;
}
-export interface IntegrationLastRun extends IntegrationRun {
- integrationId: number;
+export interface SubscriptionLastRun extends SubscriptionRun {
+ subscriptionId: number;
/** Finished runs in the recent window; in-progress runs count as neither pass nor fail. */
recentTotal: number;
recentSucceeded: number;
}
-/** One poll of a Receiving integration's own receive step — independent of the scheduler's
+/** One poll of a Receiving subscription's own receive step — independent of the scheduler's
* run history, which only knows whether the method threw (it never does; failures here are
* caught and reported this way instead). */
export type ReceiveOutcome = "Failed" | "NoNewData" | "Received";
@@ -611,12 +611,12 @@ export interface ReceiveAttemptRow {
}
/**
- * Whether a scheduled integration will actually fire, straight from the scheduler.
- * Everything here can disagree with what the integration's own record says, and
+ * Whether a scheduled subscription will actually fire, straight from the scheduler.
+ * Everything here can disagree with what the subscription's own record says, and
* when it does the job is silently dead rather than visibly broken.
*/
export interface ScheduleHealth {
- integrationId: number;
+ subscriptionId: number;
scheduleCount: number;
/** Fewer than `scheduleCount` means a schedule exists that nothing will ever fire. */
triggerCount: number;
@@ -627,10 +627,10 @@ export interface ScheduleHealth {
stuck: boolean;
}
-export interface IntegrationDetail extends Integration {
+export interface SubscriptionDetail extends Subscription {
informationTypeCode: string;
informationTypeName: string;
- /** Where this integration is plugged in (entry points). */
+ /** Where this subscription is plugged in (entry points). */
apiGatewayAttachments: { gatewayId: number; gatewayName: string; urlName: string; partnerId: number; partnerName: string }[];
busGatewayRoutes: { gatewayId: number; gatewayName: string; partnerId: number | null; partnerName: string | null }[];
watchingNotifiers: { id: number; name: string }[];
@@ -661,7 +661,7 @@ export interface WorkGroupRow extends WorkGroup {
consumerCount: number;
}
export interface WorkGroupDetail extends WorkGroup {
- integrations: IntegrationSetupRef[];
+ subscriptions: SubscriptionSetupRef[];
}
// ——— API gateways ———
@@ -669,8 +669,8 @@ export interface WorkGroupDetail extends WorkGroup {
export interface ApiGatewayAttachment {
partnerId: number;
partnerName: string;
- integrationId: number;
- integrationName: string;
+ subscriptionId: number;
+ subscriptionName: string;
}
export interface ApiGateway {
id: number;
@@ -692,8 +692,8 @@ export interface ApiGatewayDetail extends ApiGateway {
export interface BusGatewayRoute {
id: number;
- integrationId: number;
- integrationName: string;
+ subscriptionId: number;
+ subscriptionName: string;
partnerId: number | null;
partnerName: string | null;
/** null = route matches every message of the gateway's type. */
@@ -782,8 +782,8 @@ export interface ExchangeFileRef {
export interface ExchangeRow {
id: string;
status: ExchangeStatus;
- integrationId: number | null;
- integrationName: string | null;
+ subscriptionId: number | null;
+ subscriptionName: string | null;
informationTypeId: number;
informationTypeCode: string;
partnerId: number | null;
@@ -800,7 +800,7 @@ export interface ExchangeRow {
scheduledRetryOn: string | null;
exception: string | null;
promotedProperties: Record | null;
- /** True when the integration has no mapper — the Mapped stage is skipped. */
+ /** True when the subscription has no mapper — the Mapped stage is skipped. */
mapperSkipped: boolean;
files: {
input: ExchangeFileRef | null;
@@ -811,7 +811,7 @@ export interface ExchangeRow {
export interface ExchangeQuery {
status?: ExchangeStatus;
- integrationId?: number;
+ subscriptionId?: number;
partnerId?: number;
informationTypeId?: number;
/** Comma/pipe/newline separated; matches id, retryFor OR aggregationXchangeId. */
@@ -843,8 +843,8 @@ export interface ScheduledRetryRow {
id: string;
/** When the retry job will pick it up. */
on: string;
- integrationId: number | null;
- integrationName: string | null;
+ subscriptionId: number | null;
+ subscriptionName: string | null;
informationTypeId: number;
informationTypeCode: string;
exception: string | null;
@@ -853,8 +853,8 @@ export interface ScheduledRetryRow {
/** What the exchange carries — how a pending retry identifies itself in a list. */
promotedProperties: Record | null;
/**
- * The shared retry policy the integration currently points at. Null when the
- * policy is defined inline on the integration instead, so the integration — not
+ * The shared retry policy the subscription currently points at. Null when the
+ * policy is defined inline on the subscription instead, so the subscription — not
* the Retry policies list — is where to go and look.
*/
retryPolicyId: number | null;
@@ -862,7 +862,7 @@ export interface ScheduledRetryRow {
}
export interface ScheduledRetryQuery {
- integrationId?: number;
+ subscriptionId?: number;
informationTypeId?: number;
/** Substring match against the exception text. */
exception?: string;
@@ -984,19 +984,19 @@ export interface DashboardData {
queueAlerts: number;
/** Last 14 days, oldest first; today is the final entry. */
trafficByDay: { date: string; success: number; failed: number }[];
- /** Top integrations by 7-day traffic, busiest first. */
+ /** Top subscriptions by 7-day traffic, busiest first. */
busiest: { id: number; name: string; count: number; failed: number }[];
latestFailures: {
id: string;
status: ExchangeStatus;
- integrationId: number | null;
- integrationName: string | null;
+ subscriptionId: number | null;
+ subscriptionName: string | null;
informationTypeCode: string;
on: string;
exception: string | null;
}[];
attention: {
- failingIntegrations: { id: number; name: string; consecutiveFailures: number }[];
- pausedIntegrations: { id: number; name: string }[];
+ failingSubscriptions: { id: number; name: string; consecutiveFailures: number }[];
+ pausedSubscriptions: { id: number; name: string }[];
};
}
diff --git a/SW.Bitween.Web/ClientApp/src/components/config/AdapterConfig.tsx b/SW.Bitween.Web/ClientApp/src/components/config/AdapterConfig.tsx
index 0156c04a..bb35f0cd 100644
--- a/SW.Bitween.Web/ClientApp/src/components/config/AdapterConfig.tsx
+++ b/SW.Bitween.Web/ClientApp/src/components/config/AdapterConfig.tsx
@@ -420,7 +420,7 @@ export function AdapterConfig({
/** What "no adapter" means here, e.g. "None — payload passes through unchanged". */
noneLabel?: string;
/** When the native JSON mapper is selected, where its visual editor lives. */
- /** Null while the integration is still a draft — there is no page to open yet. */
+ /** Null while the subscription is still a draft — there is no page to open yet. */
mapperEditorHref?: string | null;
}) {
const catalog = useAdapterCatalog(kind);
diff --git a/SW.Bitween.Web/ClientApp/src/components/config/InformationTypeFields.tsx b/SW.Bitween.Web/ClientApp/src/components/config/InformationTypeFields.tsx
index 3b042f96..7d0fc26f 100644
--- a/SW.Bitween.Web/ClientApp/src/components/config/InformationTypeFields.tsx
+++ b/SW.Bitween.Web/ClientApp/src/components/config/InformationTypeFields.tsx
@@ -109,7 +109,7 @@ export function InformationTypeFields({
(null);
@@ -78,7 +78,7 @@ export function PartnerFields({
},
});
- const users = partnerId === null ? [] : (partnerIntegrations.get(partnerId) ?? []);
+ const users = partnerId === null ? [] : (partnerSubscriptions.get(partnerId) ?? []);
return (
@@ -113,9 +113,9 @@ export function PartnerFields({
rowDetails={(row) => {
if (!row.key.trim() || partnerId === null) return null;
return (
- referencesPartnerProp(s, row.key.trim()))}
- emptyText="Not referenced by any integration — safe to change or remove."
+ emptyText="Not referenced by any subscription — safe to change or remove."
/>
);
}}
diff --git a/SW.Bitween.Web/ClientApp/src/components/config/IntegrationDialog.tsx b/SW.Bitween.Web/ClientApp/src/components/config/SubscriptionDialog.tsx
similarity index 84%
rename from SW.Bitween.Web/ClientApp/src/components/config/IntegrationDialog.tsx
rename to SW.Bitween.Web/ClientApp/src/components/config/SubscriptionDialog.tsx
index b10a4879..c1ddfd14 100644
--- a/SW.Bitween.Web/ClientApp/src/components/config/IntegrationDialog.tsx
+++ b/SW.Bitween.Web/ClientApp/src/components/config/SubscriptionDialog.tsx
@@ -1,32 +1,32 @@
import { useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useQuery } from "@tanstack/react-query";
-import { api, type IntegrationType } from "../../api";
+import { api, type SubscriptionType } from "../../api";
import { Button, FormError } from "../ui/basics";
import { Field, TextInput } from "../ui/forms";
import { Dialog } from "../ui/overlays";
import { CodeBadge } from "../ui/Panel";
import { AdapterConfig, useAdapterCatalog } from "./AdapterConfig";
import { InfoTypePicker } from "./pickers";
-import { adapterIncomplete } from "../../pages/integrations/studio/faces";
+import { adapterIncomplete } from "../../pages/subscriptions/studio/faces";
/**
- * A new gateway-backed integration, asked down to what it cannot run without: a
+ * A new gateway-backed subscription, asked down to what it cannot run without: a
* name, the information type it carries, and somewhere to deliver.
*
* Deliberately not the whole pipeline. Transformation, validation and response are
- * nodes on the integration's own studio the moment it exists, and reproducing the
+ * nodes on the subscription's own studio the moment it exists, and reproducing the
* rail inside a modal would be a worse copy of a surface that already works. The
* dialog closes on create and hands the id back, so the picker that opened it
- * selects the new integration and you carry on.
+ * selects the new subscription and you carry on.
*/
-export function IntegrationDialog({
+export function SubscriptionDialog({
type,
informationTypeId,
onClose,
onCreated,
}: {
- type: Extract;
+ type: Extract;
/** Fixed by the caller (a bus gateway's own type); otherwise it is asked for. */
informationTypeId?: number;
onClose: () => void;
@@ -49,7 +49,7 @@ export function IntegrationDialog({
const create = useMutation({
mutationFn: () =>
- api.createIntegration({
+ api.createSubscription({
type,
name: name.trim(),
informationTypeId: pickedTypeId!,
@@ -61,9 +61,9 @@ export function IntegrationDialog({
enabled: true,
}),
onSuccess: (created) => {
- void queryClient.invalidateQueries({ queryKey: ["integrations"] });
- void queryClient.invalidateQueries({ queryKey: ["integration-rows"] });
- void queryClient.invalidateQueries({ queryKey: ["integration-rows-search"] });
+ void queryClient.invalidateQueries({ queryKey: ["subscriptions"] });
+ void queryClient.invalidateQueries({ queryKey: ["subscription-rows"] });
+ void queryClient.invalidateQueries({ queryKey: ["subscription-rows-search"] });
onCreated(created.id);
onClose();
},
@@ -77,7 +77,7 @@ export function IntegrationDialog({
].filter((m): m is string => typeof m === "string");
return (
-
diff --git a/SW.Bitween.Web/ClientApp/src/components/config/WorkGroupDialog.tsx b/SW.Bitween.Web/ClientApp/src/components/config/WorkGroupDialog.tsx
index 3d49b1ae..1c4a765f 100644
--- a/SW.Bitween.Web/ClientApp/src/components/config/WorkGroupDialog.tsx
+++ b/SW.Bitween.Web/ClientApp/src/components/config/WorkGroupDialog.tsx
@@ -10,7 +10,7 @@ import { suggestSlug } from "../../lib/identifiers";
/**
* A work group's editable settings, as one component.
*
- * Shared by the group's own page and the dialog opened from an integration, so
+ * Shared by the group's own page and the dialog opened from a subscription, so
* there is one definition of what a work group is. Its live queue stats and
* used-by list stay on the page.
*/
@@ -115,7 +115,7 @@ export function WorkGroupFields({
const EMPTY: WorkGroupDraft = { name: "", busMessageName: "", prefetch: 10, priority: 5 };
-/** A work group, created or edited in place — reached from an integration's lane picker. */
+/** A work group, created or edited in place — reached from a subscription's lane picker. */
export function WorkGroupDialog({
groupId,
onClose,
@@ -172,7 +172,7 @@ export function WorkGroupDialog({
) : (
- Gives its own queue, priority and prefetch to whatever integrations you assign to it.
+ Gives its own queue, priority and prefetch to whatever subscriptions you assign to it.
{save.error?.message}
diff --git a/SW.Bitween.Web/ClientApp/src/components/config/pickers.tsx b/SW.Bitween.Web/ClientApp/src/components/config/pickers.tsx
index 9f23d49c..906e3839 100644
--- a/SW.Bitween.Web/ClientApp/src/components/config/pickers.tsx
+++ b/SW.Bitween.Web/ClientApp/src/components/config/pickers.tsx
@@ -6,9 +6,9 @@ import { api, type InformationTypeRow } from "../../api";
import { useSessionCan } from "../../auth/guards";
import { SearchSelect } from "../ui/SearchSelect";
import { InformationTypeDialog } from "./InformationTypeDialog";
-import { IntegrationDialog } from "./IntegrationDialog";
+import { SubscriptionDialog } from "./SubscriptionDialog";
import { PartnerDialog } from "./PartnerDialog";
-import { useIntegrationsCache } from "./shared";
+import { useSubscriptionsCache } from "./shared";
/*
* Pick-one controls used inside flows. Creating or amending the thing you are
@@ -123,35 +123,35 @@ export function InfoTypePicker({
}
/**
- * Pick-one GatewayApiCall/BusGateway integration behind an entry point (an API
+ * Pick-one GatewayApiCall/BusGateway subscription behind an entry point (an API
* gateway attachment or a bus gateway route). Creating one opens a dialog.
*/
-export function IntegrationPicker({
+export function SubscriptionPicker({
type,
informationTypeId,
value,
onChange,
id,
/**
- * Given, "New integration" defines one where the caller stands instead of opening a
+ * Given, "New subscription" defines one where the caller stands instead of opening a
* dialog — the caller renders its fields and saves it with whatever points at it.
*/
onDefineHere,
}: {
type: "GatewayApiCall" | "BusGateway";
- /** Bus routes only run integrations carrying the gateway's own information type. */
+ /** Bus routes only run subscriptions carrying the gateway's own information type. */
informationTypeId?: number;
value: number | null;
onChange: (id: number) => void;
id?: string;
onDefineHere?: () => void;
}) {
- const integrations = useIntegrationsCache();
+ const subscriptions = useSubscriptionsCache();
const infoTypes = useQuery({ queryKey: ["information-types"], queryFn: () => api.listInformationTypes() });
const canCreate = useSessionCan("subscriptions.create");
const [creating, setCreating] = useState(false);
- const candidates = (integrations.data ?? []).filter(
+ const candidates = (subscriptions.data ?? []).filter(
(s) => s.type === type && (informationTypeId === undefined || s.informationTypeId === informationTypeId),
);
@@ -159,27 +159,27 @@ export function IntegrationPicker({
v !== "" && onChange(Number(v))}
- placeholder="Pick an integration…"
+ placeholder="Pick a subscription…"
options={candidates.map((s) => ({
value: String(s.id),
label: s.name,
hint: `Carries ${infoTypes.data?.find((t) => t.id === s.informationTypeId)?.code ?? "…"}`,
}))}
/>
- {/* An integration keeps a "View": its page holds run history and traffic that
+ {/* A subscription keeps a "View": its page holds run history and traffic that
no dialog is going to show, and going there is the user's own choice. */}
(onDefineHere ? onDefineHere() : setCreating(true)),
},
@@ -188,7 +188,7 @@ export function IntegrationPicker({
}
/>
{creating && (
- setCreating(false)}
diff --git a/SW.Bitween.Web/ClientApp/src/components/config/shared.tsx b/SW.Bitween.Web/ClientApp/src/components/config/shared.tsx
index 4895b778..66c49e95 100644
--- a/SW.Bitween.Web/ClientApp/src/components/config/shared.tsx
+++ b/SW.Bitween.Web/ClientApp/src/components/config/shared.tsx
@@ -4,10 +4,10 @@ import { useQuery } from "@tanstack/react-query";
import {
api,
type ExchangeRef,
- type IntegrationInfo,
- type IntegrationRow,
- type IntegrationSetupRef,
- type IntegrationType,
+ type SubscriptionInfo,
+ type SubscriptionRow,
+ type SubscriptionSetupRef,
+ type SubscriptionType,
type QueueSeverity,
type ScheduleHealth,
type TrailEntry,
@@ -19,13 +19,13 @@ import { MiniTable, type Column } from "../ui/Table";
import { formatDate, timeAgo } from "../../lib/dates";
/**
- * Display names for integration types; Internal and ApiCall are legacy.
+ * Display names for subscription types; Internal and ApiCall are legacy.
*
* `Receiving` reads "Scheduled job", not the backend's "Receiver" — it is the
* same thing the sidebar and its own page call a scheduled job, and one entity
* with two names in the same screen is just a puzzle for the reader.
*/
-export const INTEGRATION_TYPE_LABELS: Record = {
+export const SUBSCRIPTION_TYPE_LABELS: Record = {
Receiving: "Scheduled job",
GatewayApiCall: "API gateway",
BusGateway: "Bus gateway",
@@ -34,20 +34,20 @@ export const INTEGRATION_TYPE_LABELS: Record = {
Aggregation: "Aggregation",
};
-export const isLegacyType = (type: IntegrationType) =>
+export const isLegacyType = (type: SubscriptionType) =>
type === "Internal" || type === "ApiCall";
-export function TypeBadge({ type }: { type: IntegrationType }) {
+export function TypeBadge({ type }: { type: SubscriptionType }) {
return (
- {INTEGRATION_TYPE_LABELS[type]}
+ {SUBSCRIPTION_TYPE_LABELS[type]}
{isLegacyType(type) && Legacy}
);
}
-/** Enabled/paused pair — an integration can be both enabled and paused. */
-export function IntegrationStatusBadges({
+/** Enabled/paused pair — a subscription can be both enabled and paused. */
+export function SubscriptionStatusBadges({
enabled,
paused,
}: {
@@ -75,7 +75,7 @@ export function IntegrationStatusBadges({
/**
* A fault the scheduler itself reports, which contradicts whatever the
- * integration's own badges say — "Active" with no trigger behind it is still a
+ * subscription's own badges say — "Active" with no trigger behind it is still a
* job that never runs. Shared by the scheduled-jobs table and the pipeline rail
* so the two can't drift apart.
*/
@@ -112,7 +112,7 @@ export function scheduleFault(
label: "Trigger paused",
tone: "warn",
title:
- "Paused inside the scheduler — this is not the integration's own pause.",
+ "Paused inside the scheduler — this is not the subscription's own pause.",
};
case "Blocked":
return {
@@ -250,7 +250,7 @@ export function PromotedProps({
}
/**
- * Recent exchanges for one partner / information type / integration.
+ * Recent exchanges for one partner / information type / subscription.
*
* Leads with the promoted properties, because they are the only column that says
* what the exchange *was*. The id led before and answered the question nobody
@@ -350,16 +350,16 @@ export function ExchangesList({
);
}
-/** Integrations referencing this entity, each linking to its page. */
-export function SetupList({ items }: { items: IntegrationSetupRef[] }) {
+/** Subscriptions referencing this entity, each linking to its page. */
+export function SetupList({ items }: { items: SubscriptionSetupRef[] }) {
return (
s.id}
- empty="Not used by any integration."
+ empty="Not used by any subscription."
columns={[
{
- header: "Integration",
+ header: "Subscription",
truncate: true,
cell: (s) => (
api.listIntegrations(),
+ queryKey: ["subscriptions"],
+ queryFn: () => api.listSubscriptions(),
staleTime: Infinity,
});
}
/**
- * Which integrations each partner is reached through, keyed by partner id.
+ * Which subscriptions each partner is reached through, keyed by partner id.
*
* A subscription's own `partnerId` only covers the legacy types. Everything
* modern links a partner through a **gateway** — an API-gateway attachment or a
* bus route — so those have to be folded in or a partner that is plainly in use
* shows up as unused. Both gateway lists are the same cache entries the
- * Integrations page fills, and each is gated on its own view permission.
+ * Subscriptions page fills, and each is gated on its own view permission.
*/
-export function usePartnerIntegrations(): Map {
- const integrations = useIntegrationsCache().data ?? [];
+export function usePartnerSubscriptions(): Map {
+ const subscriptions = useSubscriptionsCache().data ?? [];
const canSeeApi = useSessionCan("api-gateways.view");
const canSeeBus = useSessionCan("bus-gateways.view");
const apiGateways =
@@ -419,11 +419,11 @@ export function usePartnerIntegrations(): Map {
}).data ?? [];
return useMemo(() => {
- const byId = new Map(integrations.map((s) => [s.id, s]));
- const out = new Map();
- const add = (partnerId: number | null, integrationId: number) => {
+ const byId = new Map(subscriptions.map((s) => [s.id, s]));
+ const out = new Map();
+ const add = (partnerId: number | null, subscriptionId: number) => {
if (partnerId === null) return;
- const setup = byId.get(integrationId);
+ const setup = byId.get(subscriptionId);
if (!setup) return;
const list = out.get(partnerId) ?? [];
if (!list.some((x) => x.id === setup.id)) {
@@ -431,22 +431,22 @@ export function usePartnerIntegrations(): Map {
out.set(partnerId, list);
}
};
- for (const s of integrations)
+ for (const s of subscriptions)
for (const pid of s.partnerIds) add(pid, s.id);
for (const g of apiGateways)
- for (const a of g.attachments) add(a.partnerId, a.integrationId);
+ for (const a of g.attachments) add(a.partnerId, a.subscriptionId);
for (const g of busGateways)
- for (const r of g.routes) add(r.partnerId, r.integrationId);
+ for (const r of g.routes) add(r.partnerId, r.subscriptionId);
return out;
- }, [integrations, apiGateways, busGateways]);
+ }, [subscriptions, apiGateways, busGateways]);
}
/**
- * The same wiring as `usePartnerIntegrations`, read the other way: partners
- * reached through a gateway, keyed by *integration* id.
+ * The same wiring as `usePartnerSubscriptions`, read the other way: partners
+ * reached through a gateway, keyed by *subscription* id.
*
- * `IntegrationRow.partners` only carries a subscription's own `partnerId`, which
- * the modern types never have — without this, every gateway-fed integration
+ * `SubscriptionRow.partners` only carries a subscription's own `partnerId`, which
+ * the modern types never have — without this, every gateway-fed subscription
* shows a dash where its partner should be.
*/
export function useGatewayPartners(): Map<
@@ -471,33 +471,33 @@ export function useGatewayPartners(): Map<
return useMemo(() => {
const out = new Map();
const add = (
- integrationId: number,
+ subscriptionId: number,
partnerId: number | null,
partnerName: string | null,
) => {
if (partnerId === null || partnerName === null) return;
- const list = out.get(integrationId) ?? [];
+ const list = out.get(subscriptionId) ?? [];
if (!list.some((p) => p.id === partnerId)) {
list.push({ id: partnerId, name: partnerName });
- out.set(integrationId, list);
+ out.set(subscriptionId, list);
}
};
for (const g of apiGateways)
for (const a of g.attachments)
- add(a.integrationId, a.partnerId, a.partnerName);
+ add(a.subscriptionId, a.partnerId, a.partnerName);
for (const g of busGateways)
for (const r of g.routes)
- add(r.integrationId, r.partnerId, r.partnerName);
+ add(r.subscriptionId, r.partnerId, r.partnerName);
return out;
}, [apiGateways, busGateways]);
}
-/** Live status for every integration, keyed by id — shared by the gateway pages. */
-export function useIntegrationRowsById(): Map {
+/** Live status for every subscription, keyed by id — shared by the gateway pages. */
+export function useSubscriptionRowsById(): Map {
const rows =
useQuery({
- queryKey: ["integration-rows"],
- queryFn: () => api.listIntegrationRows(),
+ queryKey: ["subscription-rows"],
+ queryFn: () => api.listSubscriptionRows(),
}).data ?? [];
return useMemo(() => new Map(rows.map((r) => [r.id, r])), [rows]);
}
@@ -538,28 +538,28 @@ export function WiredHealthBadge({
rows,
empty,
}: {
- rows: IntegrationRow[];
+ rows: SubscriptionRow[];
empty: string;
}) {
if (rows.length === 0) return {empty};
const failing = rows.filter((r) => r.consecutiveFailures > 0).length;
if (failing > 0)
return (
-
+
{failing} failing
);
const paused = rows.filter((r) => r.paused).length;
if (paused > 0)
return (
-
+
{paused} paused
);
const disabled = rows.filter((r) => !r.enabled).length;
if (disabled > 0)
return (
- {disabled} disabled
+ {disabled} disabled
);
return (
@@ -572,21 +572,21 @@ export function WiredHealthBadge({
* The columns describing the pipeline behind one gateway attachment or route.
*
* These tables are the only 1:1 place in the gateway story — one row is exactly
- * one partner and one integration — so this is where its configuration can be
+ * one partner and one subscription — so this is where its configuration can be
* stated in separate columns without the reader having to guess which value
* pairs with which. The parent list can't do it: two parallel lists in a row
* lose their pairing, which is why the gateway tables carry only aggregates.
*
- * Everything here comes from caches the app already holds, keyed by integration
+ * Everything here comes from caches the app already holds, keyed by subscription
* id; the gateway endpoints know none of it.
*/
-export function useWiredIntegrationColumns(
- integrationIdOf: (row: T) => number,
+export function useWiredSubscriptionColumns(
+ subscriptionIdOf: (row: T) => number,
/** Off where the parent already fixes it — a bus gateway listens for one type. */
{ informationType = true }: { informationType?: boolean } = {},
): Column[] {
- const rowsById = useIntegrationRowsById();
- const setups = useIntegrationsCache().data ?? [];
+ const rowsById = useSubscriptionRowsById();
+ const setups = useSubscriptionsCache().data ?? [];
const setupById = useMemo(
() => new Map(setups.map((s) => [s.id, s])),
[setups],
@@ -601,7 +601,7 @@ export function useWiredIntegrationColumns(
columns.push({
header: "Information type",
cell: (row) => {
- const r = rowsById.get(integrationIdOf(row));
+ const r = rowsById.get(subscriptionIdOf(row));
if (!r) return —;
return canSeeInfoTypes ? (
(
{
header: "Work group",
cell: (row) => {
- const id = setupById.get(integrationIdOf(row))?.workGroupId ?? null;
+ const id = setupById.get(subscriptionIdOf(row))?.workGroupId ?? null;
// "Ungrouped", not "Default": a null WorkGroupId isn't the absence of a
// lane, it's `WorkGroup.None` — a real shared queue (`0Ungrouped`) that
- // every ungrouped integration competes in. Matches the wording the
- // integration page's work-group picker already uses.
+ // every ungrouped subscription competes in. Matches the wording the
+ // subscription page's work-group picker already uses.
if (id === null)
return Ungrouped;
const name = workGroupNames.get(id);
@@ -645,7 +645,7 @@ export function useWiredIntegrationColumns(
{
header: "Retry policy",
cell: (row) => {
- const id = setupById.get(integrationIdOf(row))?.retryPolicyId ?? null;
+ const id = setupById.get(subscriptionIdOf(row))?.retryPolicyId ?? null;
if (id === null)
return None;
const name = retryPolicyNames.get(id);
@@ -664,11 +664,11 @@ export function useWiredIntegrationColumns(
{
header: "Status",
cell: (row) => {
- const r = rowsById.get(integrationIdOf(row));
+ const r = rowsById.get(subscriptionIdOf(row));
if (!r) return —;
return (
-
+ (
// unbounded stack trace would push everything else out of the panel.
className: "max-w-48 overflow-hidden",
cell: (row) => {
- const message = rowsById.get(integrationIdOf(row))?.lastException;
+ const message = rowsById.get(subscriptionIdOf(row))?.lastException;
return message ? (
—;
@@ -785,16 +785,16 @@ export function LinkListCell({
);
}
-/** `LinkListCell` for the commonest case: the integrations using something. */
-export function UsedByCell({ items }: { items: IntegrationInfo[] }) {
+/** `LinkListCell` for the commonest case: the subscriptions using something. */
+export function UsedByCell({ items }: { items: SubscriptionInfo[] }) {
return (
({
key: s.id,
name: s.name,
href: `/subscriptions/${s.id}`,
- note: {INTEGRATION_TYPE_LABELS[s.type]},
+ note: {SUBSCRIPTION_TYPE_LABELS[s.type]},
}))}
/>
);
@@ -842,12 +842,12 @@ export function TrailTable({ entries }: { entries: TrailEntry[] }) {
);
}
-/** Integrations referencing one particular key or value, with their type. */
-export function IntegrationMiniList({
+/** Subscriptions referencing one particular key or value, with their type. */
+export function SubscriptionMiniList({
items,
emptyText,
}: {
- items: IntegrationInfo[];
+ items: SubscriptionInfo[];
emptyText: string;
}) {
return (
@@ -857,7 +857,7 @@ export function IntegrationMiniList({
empty={emptyText}
columns={[
{
- header: "Integration",
+ header: "Subscription",
truncate: true,
cell: (s) => (
{INTEGRATION_TYPE_LABELS[s.type]},
+ cell: (s) => {SUBSCRIPTION_TYPE_LABELS[s.type]},
},
]}
/>
diff --git a/SW.Bitween.Web/ClientApp/src/components/mapper/useMappingEditorLoader.ts b/SW.Bitween.Web/ClientApp/src/components/mapper/useMappingEditorLoader.ts
index 97ffaadb..99a2f1aa 100644
--- a/SW.Bitween.Web/ClientApp/src/components/mapper/useMappingEditorLoader.ts
+++ b/SW.Bitween.Web/ClientApp/src/components/mapper/useMappingEditorLoader.ts
@@ -12,8 +12,8 @@ import { recordToKvps } from "./data";
export function useMappingEditorLoader(subscriptionId: number): void {
const dispatch = useMappingEditorDispatch();
const { data: subscriptionData } = useQuery({
- queryKey: ["integration", subscriptionId],
- queryFn: () => api.getIntegration(subscriptionId),
+ queryKey: ["subscription", subscriptionId],
+ queryFn: () => api.getSubscription(subscriptionId),
enabled: !!subscriptionId,
});
const [, setLoadedForId] = useState(null);
diff --git a/SW.Bitween.Web/ClientApp/src/components/mapper/useSave.ts b/SW.Bitween.Web/ClientApp/src/components/mapper/useSave.ts
index 63117a16..c94839e2 100644
--- a/SW.Bitween.Web/ClientApp/src/components/mapper/useSave.ts
+++ b/SW.Bitween.Web/ClientApp/src/components/mapper/useSave.ts
@@ -41,11 +41,11 @@ export function useSave(subscriptionId: number): UseSaveResult {
} = useMappingEditorState();
const saveMapper = useMutation({
mutationFn: (props: KeyValuePair[]) =>
- api.updateIntegration(subscriptionId, {
+ api.updateSubscription(subscriptionId, {
mapperId: NATIVE_JSON_MAPPER_ID,
mapperProperties: kvpsToRecord(props),
}),
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ["integration", subscriptionId] }),
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["subscription", subscriptionId] }),
});
const isSaving = saveMapper.isPending;
const [saveSuccess, setSaveSuccess] = useState(false);
diff --git a/SW.Bitween.Web/ClientApp/src/components/ui/BackLink.tsx b/SW.Bitween.Web/ClientApp/src/components/ui/BackLink.tsx
index dfc08bdb..8bd6bccc 100644
--- a/SW.Bitween.Web/ClientApp/src/components/ui/BackLink.tsx
+++ b/SW.Bitween.Web/ClientApp/src/components/ui/BackLink.tsx
@@ -7,7 +7,7 @@ import { ArrowLeft } from "lucide-react";
* These were all fixed `Link`s to a list page, which is only right when the list is
* where you came from. `subscriptions/:id` is reached from Scheduled jobs, Exchanges,
* an API gateway's page, the retry usage panel and three places on the dashboard — and
- * from every one of them "← Integrations" landed you somewhere you had never been.
+ * from every one of them "← Subscriptions" landed you somewhere you had never been.
*
* So it steps back when there is somewhere to step back to, and falls back to `to` when
* there isn't: a pasted link, a new tab, a refresh. The label follows the behaviour
diff --git a/SW.Bitween.Web/ClientApp/src/lib/busMessageName.ts b/SW.Bitween.Web/ClientApp/src/lib/busMessageName.ts
index 3e7bfe6c..7c03c243 100644
--- a/SW.Bitween.Web/ClientApp/src/lib/busMessageName.ts
+++ b/SW.Bitween.Web/ClientApp/src/lib/busMessageName.ts
@@ -2,7 +2,7 @@
* The one rule for a bus message name, and the one way of saying it.
*
* Two fields name the same thing — an information type's `busMessageTypeName` and an
- * integration's `responseMessageTypeName` — and they had drifted: one silently deleted
+ * subscription's `responseMessageTypeName` — and they had drifted: one silently deleted
* spaces as you typed, the other refused them and said why, and only one mentioned the
* rule at all. Whatever the rule becomes, both fields read it from here.
*/
diff --git a/SW.Bitween.Web/ClientApp/src/nav.ts b/SW.Bitween.Web/ClientApp/src/nav.ts
index 7012f172..aef57eaf 100644
--- a/SW.Bitween.Web/ClientApp/src/nav.ts
+++ b/SW.Bitween.Web/ClientApp/src/nav.ts
@@ -55,8 +55,9 @@ export const NAV_GROUPS: NavGroup[] = [
},
{
// The overview first, then entry points — how a document gets in — then the
- // pipelines it runs through, then who it's with. A gateway is not an integration.
- label: "Integrations",
+ // pipelines it runs through, then who it's with. The heading names the area, not the
+ // entity: a gateway is not itself a subscription.
+ label: "Subscriptions",
items: [
{ label: "API gateways", path: "/api-gateways", icon: Webhook, permissions: ["api-gateways.view"] },
{ label: "Bus gateways", path: "/bus-gateways", icon: Cable, permissions: ["bus-gateways.view"] },
@@ -65,7 +66,7 @@ export const NAV_GROUPS: NavGroup[] = [
// rather than a fourth kind of them. Gated on the bus alone: bus messages are what
// carry work *between* gateways, so without that permission there is no flow to map.
{ label: "Flow map", path: "/flow", icon: Network, permissions: ["bus-gateways.view"] },
- { label: "All integrations", path: "/subscriptions", icon: Workflow, permissions: ["subscriptions.view"] },
+ { label: "All subscriptions", path: "/subscriptions", icon: Workflow, permissions: ["subscriptions.view"] },
{ label: "Partners", path: "/partners", icon: Handshake, permissions: ["partners.view"] },
],
},
diff --git a/SW.Bitween.Web/ClientApp/src/pages/api-gateways/ApiGatewayPage.tsx b/SW.Bitween.Web/ClientApp/src/pages/api-gateways/ApiGatewayPage.tsx
index 5b4dc2cd..0f046872 100644
--- a/SW.Bitween.Web/ClientApp/src/pages/api-gateways/ApiGatewayPage.tsx
+++ b/SW.Bitween.Web/ClientApp/src/pages/api-gateways/ApiGatewayPage.tsx
@@ -12,7 +12,7 @@ import { CopyField } from "../../components/ui/CopyField";
import { EditableTitle, Panel, UnsavedBar } from "../../components/ui/Panel";
import { MiniTable } from "../../components/ui/Table";
import { Pagination } from "../../components/ui/Pagination";
-import { useWiredIntegrationColumns } from "../../components/config/shared";
+import { useWiredSubscriptionColumns } from "../../components/config/shared";
import { BackLink } from "../../components/ui/BackLink";
const ATTACHMENTS_PAGE_SIZE = 10;
@@ -24,7 +24,7 @@ export function ApiGatewayPage() {
const queryClient = useQueryClient();
const [searchParams, setSearchParams] = useSearchParams();
const canEdit = useSessionCan("api-gateways.edit");
- const wiredColumns = useWiredIntegrationColumns((a) => a.integrationId);
+ const wiredColumns = useWiredSubscriptionColumns((a) => a.subscriptionId);
const gateway = useQuery({
queryKey: ["api-gateway", gatewayId],
@@ -160,7 +160,7 @@ export function ApiGatewayPage() {