Skip to content

Commit 8fc5d2d

Browse files
committed
feat: instatus importer
1 parent 76e68b1 commit 8fc5d2d

File tree

16 files changed

+1387
-33
lines changed

16 files changed

+1387
-33
lines changed

apps/dashboard/src/components/forms/components/form-import.tsx

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
} from "@/components/forms/form-card";
1313
import { useTRPC } from "@/lib/trpc/client";
1414
import { zodResolver } from "@hookform/resolvers/zod";
15-
import { StatuspageIcon } from "@openstatus/icons";
15+
import { InstatusIcon, StatuspageIcon } from "@openstatus/icons";
1616
import type { ImportSummary } from "@openstatus/importers/types";
1717
import { Badge } from "@openstatus/ui/components/ui/badge";
1818
import { Button } from "@openstatus/ui/components/ui/button";
@@ -40,9 +40,10 @@ import { toast } from "sonner";
4040
import { z } from "zod";
4141

4242
const schema = z.object({
43-
provider: z.enum(["statuspage"]),
43+
provider: z.enum(["statuspage", "instatus"]),
4444
apiKey: z.string().min(1, "API key is required"),
4545
statuspagePageId: z.string().optional(),
46+
instatusPageId: z.string().optional(),
4647
includeStatusReports: z.boolean(),
4748
includeSubscribers: z.boolean(),
4849
includeComponents: z.boolean(),
@@ -75,6 +76,7 @@ export function FormImport({
7576
provider: undefined,
7677
apiKey: "",
7778
statuspagePageId: "",
79+
instatusPageId: "",
7880
includeStatusReports: true,
7981
includeSubscribers: false,
8082
includeComponents: true,
@@ -85,6 +87,7 @@ export function FormImport({
8587
const watchProvider = form.watch("provider");
8688
const watchApiKey = form.watch("apiKey");
8789
const watchStatuspagePageId = form.watch("statuspagePageId");
90+
const watchInstatusPageId = form.watch("instatusPageId");
8891

8992
const previewMutation = useMutation(
9093
trpc.import.preview.mutationOptions({
@@ -105,9 +108,16 @@ export function FormImport({
105108
return;
106109
}
107110
previewMutation.mutate({
108-
provider: "statuspage",
111+
provider: watchProvider,
109112
apiKey: watchApiKey,
110-
statuspagePageId: watchStatuspagePageId || undefined,
113+
statuspagePageId:
114+
watchProvider === "statuspage"
115+
? watchStatuspagePageId || undefined
116+
: undefined,
117+
instatusPageId:
118+
watchProvider === "instatus"
119+
? watchInstatusPageId || undefined
120+
: undefined,
111121
pageId,
112122
});
113123
}
@@ -178,6 +188,21 @@ export function FormImport({
178188
Atlassian Statuspage
179189
</FormLabel>
180190
</FormItem>
191+
<FormItem className="relative flex cursor-pointer flex-row items-center gap-3 rounded-md border border-input px-2 py-3 text-center shadow-xs outline-none transition-[color,box-shadow] has-data-[state=checked]:border-primary/50 has-focus-visible:border-ring has-focus-visible:ring-[3px] has-focus-visible:ring-ring/50">
192+
<FormControl>
193+
<RadioGroupItem
194+
value="instatus"
195+
className="sr-only"
196+
/>
197+
</FormControl>
198+
<InstatusIcon
199+
className="size-4 shrink-0 text-foreground"
200+
aria-hidden="true"
201+
/>
202+
<FormLabel className="cursor-pointer font-medium text-foreground text-xs leading-none after:absolute after:inset-0">
203+
Instatus
204+
</FormLabel>
205+
</FormItem>
181206
<div className="col-span-1 self-end text-muted-foreground text-xs sm:place-self-end">
182207
Missing a provider?{" "}
183208
<a href="mailto:ping@openstatus.dev">Contact us</a>
@@ -202,34 +227,59 @@ export function FormImport({
202227
<FormControl>
203228
<Input
204229
type="password"
205-
placeholder="OAuth API key"
230+
placeholder={
231+
watchProvider === "instatus"
232+
? "Bearer API key"
233+
: "OAuth API key"
234+
}
206235
{...field}
207236
/>
208237
</FormControl>
209238
<FormMessage />
210239
<FormDescription>
211-
Your Statuspage API key. Found in your Statuspage
212-
account under Manage Account &gt; API.
213-
</FormDescription>
214-
</FormItem>
215-
)}
216-
/>
217-
<FormField
218-
control={form.control}
219-
name="statuspagePageId"
220-
render={({ field }) => (
221-
<FormItem>
222-
<FormLabel>Page ID (optional)</FormLabel>
223-
<FormControl>
224-
<Input placeholder="e.g. abc123def456" {...field} />
225-
</FormControl>
226-
<FormDescription>
227-
Import a specific page. Leave empty to import across
228-
pages.
240+
{watchProvider === "instatus"
241+
? "Your Instatus API key. Found in your Instatus account under Settings > API."
242+
: "Your Statuspage API key. Found in your Statuspage account under Manage Account > API."}
229243
</FormDescription>
230244
</FormItem>
231245
)}
232246
/>
247+
{watchProvider === "statuspage" ? (
248+
<FormField
249+
control={form.control}
250+
name="statuspagePageId"
251+
render={({ field }) => (
252+
<FormItem>
253+
<FormLabel>Page ID (optional)</FormLabel>
254+
<FormControl>
255+
<Input placeholder="e.g. abc123def456" {...field} />
256+
</FormControl>
257+
<FormDescription>
258+
Import a specific page. Leave empty to import across
259+
pages.
260+
</FormDescription>
261+
</FormItem>
262+
)}
263+
/>
264+
) : null}
265+
{watchProvider === "instatus" ? (
266+
<FormField
267+
control={form.control}
268+
name="instatusPageId"
269+
render={({ field }) => (
270+
<FormItem>
271+
<FormLabel>Page ID (optional)</FormLabel>
272+
<FormControl>
273+
<Input placeholder="e.g. clx1abc2def3" {...field} />
274+
</FormControl>
275+
<FormDescription>
276+
Import a specific page. Leave empty to import the
277+
first page.
278+
</FormDescription>
279+
</FormItem>
280+
)}
281+
/>
282+
) : null}
233283
<Button
234284
type="button"
235285
variant="secondary"

packages/api/src/router/import.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@ export const importRouter = createTRPCRouter({
99
preview: protectedProcedure
1010
.input(
1111
z.object({
12-
provider: z.enum(["statuspage"]),
12+
provider: z.enum(["statuspage", "instatus"]),
1313
apiKey: z.string().min(1),
1414
statuspagePageId: z.string().nullish(),
15+
instatusPageId: z.string().nullish(),
1516
pageId: z.number().optional(),
1617
}),
1718
)
1819
.mutation(async (opts) => {
1920
return previewImport({
21+
provider: opts.input.provider,
2022
apiKey: opts.input.apiKey,
2123
statuspagePageId: opts.input.statuspagePageId ?? undefined,
24+
instatusPageId: opts.input.instatusPageId ?? undefined,
2225
workspaceId: opts.ctx.workspace.id,
2326
pageId: opts.input.pageId,
2427
limits: opts.ctx.workspace.limits,
@@ -28,10 +31,11 @@ export const importRouter = createTRPCRouter({
2831
run: protectedProcedure
2932
.input(
3033
z.object({
31-
provider: z.enum(["statuspage"]),
34+
provider: z.enum(["statuspage", "instatus"]),
3235
apiKey: z.string().min(1),
3336
pageId: z.number().optional(),
3437
statuspagePageId: z.string().nullish(),
38+
instatusPageId: z.string().nullish(),
3539
options: z
3640
.object({
3741
includeStatusReports: z.boolean().default(true),
@@ -64,8 +68,10 @@ export const importRouter = createTRPCRouter({
6468
}
6569

6670
return runImport({
71+
provider: opts.input.provider,
6772
apiKey: opts.input.apiKey,
6873
statuspagePageId: opts.input.statuspagePageId ?? undefined,
74+
instatusPageId: opts.input.instatusPageId ?? undefined,
6975
workspaceId: opts.ctx.workspace.id,
7076
pageId: opts.input.pageId,
7177
options: opts.input.options,

packages/api/src/service/import.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
PhaseResult,
1818
ResourceResult,
1919
} from "@openstatus/importers";
20+
import { createInstatusProvider } from "@openstatus/importers/instatus";
2021
import { createStatuspageProvider } from "@openstatus/importers/statuspage";
2122
import { TRPCError } from "@trpc/server";
2223

@@ -97,16 +98,26 @@ export async function addLimitWarnings(
9798
}
9899

99100
export async function previewImport(config: {
101+
provider: "statuspage" | "instatus";
100102
apiKey: string;
101103
statuspagePageId?: string;
104+
instatusPageId?: string;
102105
workspaceId: number;
103106
pageId?: number;
104107
limits: Limits;
105108
}): Promise<ImportSummary> {
106-
const provider = createStatuspageProvider();
109+
const provider =
110+
config.provider === "instatus"
111+
? createInstatusProvider()
112+
: createStatuspageProvider();
113+
114+
const providerConfig =
115+
config.provider === "instatus"
116+
? { ...config, instatusPageId: config.instatusPageId }
117+
: { ...config, statuspagePageId: config.statuspagePageId };
107118

108119
const validation = await provider.validate({
109-
...config,
120+
...providerConfig,
110121
dryRun: true,
111122
});
112123
if (!validation.valid) {
@@ -116,22 +127,32 @@ export async function previewImport(config: {
116127
});
117128
}
118129

119-
const summary = await provider.run({ ...config, dryRun: true });
130+
const summary = await provider.run({ ...providerConfig, dryRun: true });
120131
await addLimitWarnings(summary, config);
121132
return summary;
122133
}
123134

124135
export async function runImport(config: {
136+
provider: "statuspage" | "instatus";
125137
apiKey: string;
126138
statuspagePageId?: string;
139+
instatusPageId?: string;
127140
workspaceId: number;
128141
pageId?: number;
129142
options?: ImportOptions;
130143
limits: Limits;
131144
}): Promise<ImportSummary> {
132-
const provider = createStatuspageProvider();
145+
const provider =
146+
config.provider === "instatus"
147+
? createInstatusProvider()
148+
: createStatuspageProvider();
149+
150+
const providerConfig =
151+
config.provider === "instatus"
152+
? { ...config, instatusPageId: config.instatusPageId }
153+
: { ...config, statuspagePageId: config.statuspagePageId };
133154

134-
const validation = await provider.validate(config);
155+
const validation = await provider.validate(providerConfig);
135156
if (!validation.valid) {
136157
throw new TRPCError({
137158
code: "BAD_REQUEST",
@@ -140,7 +161,7 @@ export async function runImport(config: {
140161
}
141162

142163
// Fetch and map all data
143-
const summary = await provider.run(config);
164+
const summary = await provider.run(providerConfig);
144165

145166
// Add limit warnings (same as preview)
146167
await addLimitWarnings(summary, config);

packages/icons/src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from "./telegram";
1212
export * from "./whatsapp";
1313
export * from "./markdown";
1414
export * from "./statuspage";
15+
export * from "./instatus";

packages/icons/src/instatus.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export function InstatusIcon(props: React.ComponentProps<"svg">) {
2+
return (
3+
<svg
4+
role="img"
5+
viewBox="0 0 24 24"
6+
xmlns="http://www.w3.org/2000/svg"
7+
stroke="currentColor"
8+
fill="currentColor"
9+
{...props}
10+
>
11+
<title>Instatus</title>
12+
<path d="m16.994 21.028c3.5843-1.91 5.471-5.759 5.0561-9.5637-1.3206 1.0851-2.6237 2.3203-3.8709 3.6906-2.0656 2.2694-3.7476 4.6559-4.9953 6.9817 1.2946-0.09715 2.5907-0.45868 3.8101-1.1086zm-13.394-2.5626c-1.3408 1.8191-2.3771 4.4991-1.3032 5.3066 1.5151 1.1394 8.404-2.0133 13.908-8.8051 5.504-6.7918 7.3265-13.796 4.879-14.873-1.1283-0.49644-3.486 1.083-4.8394 2.3943l0.58412 0.31415c1.332-0.85276 3.5528-1.7338 1.4995 1.9758-0.0097 0.01768-0.01962 0.03541-0.02949 0.05317-2.9067-2.2075-6.9471-2.662-10.379-0.8328-4.7026 2.506-6.4831 8.3499-3.9771 13.052 0.58979 1.1067 1.3644 2.0516 2.2655 2.8168-3.5586 2.7493-2.6905 0.35965-2.1925-0.8162z" />
13+
</svg>
14+
);
15+
}

packages/importers/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
".": "./src/index.ts",
88
"./types": "./src/types.ts",
99
"./statuspage": "./src/providers/statuspage/index.ts",
10-
"./statuspage/fixtures": "./src/providers/statuspage/fixtures.ts"
10+
"./statuspage/fixtures": "./src/providers/statuspage/fixtures.ts",
11+
"./instatus": "./src/providers/instatus/index.ts",
12+
"./instatus/fixtures": "./src/providers/instatus/fixtures.ts"
1113
},
1214
"scripts": {
1315
"test": "bun test",

packages/importers/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ export type {
99
export { createStatuspageProvider } from "./providers/statuspage";
1010
export type { StatuspageImportConfig } from "./providers/statuspage";
1111

12+
export { createInstatusProvider } from "./providers/instatus";
13+
export type { InstatusImportConfig } from "./providers/instatus";
14+
1215
/**
1316
* Registry of all available import providers.
1417
* Add new providers here as they are implemented.
1518
*/
16-
export const IMPORT_PROVIDERS = ["statuspage"] as const;
19+
export const IMPORT_PROVIDERS = ["statuspage", "instatus"] as const;
1720
export type ImportProviderName = (typeof IMPORT_PROVIDERS)[number];

0 commit comments

Comments
 (0)