diff --git a/keep-ui/.gitignore b/keep-ui/.gitignore index 42daa1e2a..6eca4f651 100644 --- a/keep-ui/.gitignore +++ b/keep-ui/.gitignore @@ -8,6 +8,8 @@ pids *.pid *.seed +!lib + # Directory for instrumented libs generated by jscoverage/JSCover lib-cov diff --git a/keep-ui/app/error.tsx b/keep-ui/app/error.tsx index 3f0c0ad7d..532fd8622 100644 --- a/keep-ui/app/error.tsx +++ b/keep-ui/app/error.tsx @@ -6,21 +6,22 @@ "use client"; import Image from "next/image"; import "./error.css"; -import {useEffect} from "react"; -import {Title, Subtitle} from "@tremor/react"; -import {Button, Text} from "@tremor/react"; +import { useEffect } from "react"; +import { Title, Subtitle } from "@tremor/react"; +import { Button, Text } from "@tremor/react"; import { signOut } from "next-auth/react"; +import { KeepApiError } from "@/shared/lib/KeepApiError"; export default function ErrorComponent({ error, reset, }: { - error: Error | KeepApiError - reset: () => void + error: Error | KeepApiError; + reset: () => void; }) { useEffect(() => { - console.error(error) - }, [error]) + console.error(error); + }, [error]); return (
@@ -29,16 +30,16 @@ export default function ErrorComponent({ {error instanceof KeepApiError && (
- Status Code: {error.statusCode}
+ Status Code: {error.statusCode} +
Message: {error.message}
)}
- {error instanceof KeepApiError && error.proposedResolution && ( - {error.proposedResolution} - ) - } + {error instanceof KeepApiError && error.proposedResolution && ( + {error.proposedResolution} + )}
Keep @@ -48,36 +49,23 @@ export default function ErrorComponent({ onClick={() => signOut()} color="orange" variant="secondary" - className="mt-4 border border-orange-500 text-orange-500"> + className="mt-4 border border-orange-500 text-orange-500" + > Sign Out ) : ( )}
- ) -} - -// Custom Error Class -export class KeepApiError extends Error { - url: string; - proposedResolution: string; - statusCode: number | undefined; - - constructor(message: string, url: string, proposedResolution: string, statusCode?: number) { - super(message); - this.name = "KeepApiError"; - this.url = url; - this.proposedResolution = proposedResolution; - this.statusCode = statusCode; - } + ); } diff --git a/keep-ui/app/incidents/incident-list.tsx b/keep-ui/app/incidents/incident-list.tsx index f0b3951e7..f94b48f9f 100644 --- a/keep-ui/app/incidents/incident-list.tsx +++ b/keep-ui/app/incidents/incident-list.tsx @@ -2,7 +2,7 @@ import { Card, Title, Subtitle, Button, Badge } from "@tremor/react"; import Loading from "app/loading"; import React, { useState } from "react"; -import { IncidentDto } from "./models"; +import { IncidentDto, PaginatedIncidentsDto } from "@/app/incidents/models"; import CreateOrUpdateIncident from "./create-or-update-incident"; import IncidentsTable from "./incidents-table"; import { useIncidents, usePollIncidents } from "utils/hooks/useIncidents"; @@ -28,7 +28,11 @@ interface Filters { affected_services: string[]; } -export default function IncidentList() { +export default function IncidentList({ + initialData, +}: { + initialData?: PaginatedIncidentsDto; +}) { const [incidentsPagination, setIncidentsPagination] = useState({ limit: 20, offset: 0, @@ -65,7 +69,12 @@ export default function IncidentList() { incidentsPagination.limit, incidentsPagination.offset, incidentsSorting[0], - filters + filters, + { + revalidateOnFocus: false, + revalidateOnMount: !initialData, + fallbackData: initialData, + } ); const { data: predictedIncidents, diff --git a/keep-ui/app/incidents/page.tsx b/keep-ui/app/incidents/page.tsx index b311caaac..b50c55b82 100644 --- a/keep-ui/app/incidents/page.tsx +++ b/keep-ui/app/incidents/page.tsx @@ -1,7 +1,32 @@ +import { getServerSession } from "next-auth/next"; import IncidentList from "./incident-list"; +import { getApiURL } from "@/utils/apiUrl"; +import { authOptions } from "@/pages/api/auth/[...nextauth]"; +import { + getIncidents, + GetIncidentsParams, +} from "@/entities/incidents/api/incidents"; +import { PaginatedIncidentsDto } from "./models"; -export default function Page() { - return ; +const defaultIncidentsParams: GetIncidentsParams = { + confirmed: true, + limit: 20, + offset: 0, + sorting: { id: "creation_time", desc: true }, + filters: {}, +}; + +export default async function Page() { + let incidents: PaginatedIncidentsDto | null = null; + try { + const session = await getServerSession(authOptions); + const apiUrl = getApiURL(); + + incidents = await getIncidents(apiUrl, session, defaultIncidentsParams); + } catch (error) { + console.log(error); + } + return ; } export const metadata = { diff --git a/keep-ui/app/providers/page.client.tsx b/keep-ui/app/providers/page.client.tsx index 68ce8aab8..258df36a7 100644 --- a/keep-ui/app/providers/page.client.tsx +++ b/keep-ui/app/providers/page.client.tsx @@ -1,7 +1,7 @@ "use client"; import { defaultProvider, Provider } from "./providers"; import { useSession } from "next-auth/react"; -import { KeepApiError } from "../error"; +import { KeepApiError } from "@/shared/lib/KeepApiError"; import { useApiUrl } from "utils/hooks/useConfig"; import ProvidersTiles from "./providers-tiles"; import React, { useState, useEffect } from "react"; diff --git a/keep-ui/app/workflows/builder/builder-card.tsx b/keep-ui/app/workflows/builder/builder-card.tsx index 5f534e312..49e565461 100644 --- a/keep-ui/app/workflows/builder/builder-card.tsx +++ b/keep-ui/app/workflows/builder/builder-card.tsx @@ -5,7 +5,7 @@ import { useEffect, useState } from "react"; import { useApiUrl } from "utils/hooks/useConfig"; import Loader from "./loader"; import { Provider } from "../../providers/providers"; -import { KeepApiError } from "../../error"; +import { KeepApiError } from "@/shared/lib/KeepApiError"; import { useProviders } from "utils/hooks/useProviders"; const Builder = dynamic(() => import("./builder"), { diff --git a/keep-ui/entities/incidents/api/incidents.ts b/keep-ui/entities/incidents/api/incidents.ts new file mode 100644 index 000000000..0b70e14ec --- /dev/null +++ b/keep-ui/entities/incidents/api/incidents.ts @@ -0,0 +1,52 @@ +import { PaginatedIncidentsDto } from "@/app/incidents/models"; +import { fetcher } from "@/utils/fetcher"; +import { Session } from "next-auth"; + +interface Filters { + status: string[]; + severity: string[]; + assignees: string[]; + sources: string[]; + affected_services: string[]; +} + +export type GetIncidentsParams = { + confirmed: boolean; + limit: number; + offset: number; + sorting: { id: string; desc: boolean }; + filters: Filters | {}; +}; + +export function buildIncidentsUrl(apiUrl: string, params: GetIncidentsParams) { + const filtersParams = new URLSearchParams(); + + Object.entries(params.filters).forEach(([key, value]) => { + if (value.length == 0) { + filtersParams.delete(key as string); + } else { + value.forEach((s: string) => { + filtersParams.append(key, s); + }); + } + }); + + return `${apiUrl}/incidents?confirmed=${params.confirmed}&limit=${params.limit}&offset=${params.offset}&sorting=${ + params.sorting.desc ? "-" : "" + }${params.sorting.id}&${filtersParams.toString()}`; +} + +export async function getIncidents( + apiUrl: string, + session: Session | null, + params: GetIncidentsParams +) { + if (!session) { + return null; + } + const url = buildIncidentsUrl(apiUrl, params); + return (await fetcher( + url, + session.accessToken + )) as Promise; +} diff --git a/keep-ui/shared/lib/KeepApiError.ts b/keep-ui/shared/lib/KeepApiError.ts new file mode 100644 index 000000000..92727a309 --- /dev/null +++ b/keep-ui/shared/lib/KeepApiError.ts @@ -0,0 +1,24 @@ +// Custom Error Class + +export class KeepApiError extends Error { + url: string; + proposedResolution: string; + statusCode: number | undefined; + + constructor( + message: string, + url: string, + proposedResolution: string, + statusCode?: number + ) { + super(message); + this.name = "KeepApiError"; + this.url = url; + this.proposedResolution = proposedResolution; + this.statusCode = statusCode; + } + + toString() { + return `${this.name}: ${this.message} - ${this.url} - ${this.proposedResolution} - ${this.statusCode}`; + } +} diff --git a/keep-ui/tailwind.config.js b/keep-ui/tailwind.config.js index 3e6a5b5f5..da660de41 100644 --- a/keep-ui/tailwind.config.js +++ b/keep-ui/tailwind.config.js @@ -5,6 +5,7 @@ module.exports = { "./components/**/*.{js,ts,jsx,tsx}", "./entities/**/*.{js,ts,jsx,tsx}", "./features/**/*.{js,ts,jsx,tsx}", + "./shared/**/*.{js,ts,jsx,tsx}", "./node_modules/@tremor/**/*.{js,ts,jsx,tsx}", ], darkMode: "class", diff --git a/keep-ui/tsconfig.json b/keep-ui/tsconfig.json index 6d7822423..302b30407 100644 --- a/keep-ui/tsconfig.json +++ b/keep-ui/tsconfig.json @@ -27,7 +27,8 @@ "@/pages/*": ["./pages/*"], "@/utils/*": ["./utils/*"], "@/entities/*": ["./entities/*"], - "@/features/*": ["./features/*"] + "@/features/*": ["./features/*"], + "@/shared/*": ["./shared/*"] } }, "include": [ diff --git a/keep-ui/utils/fetcher.ts b/keep-ui/utils/fetcher.ts index 1b1446b1b..6a0287675 100644 --- a/keep-ui/utils/fetcher.ts +++ b/keep-ui/utils/fetcher.ts @@ -1,4 +1,4 @@ -import { KeepApiError } from '../app/error'; +import { KeepApiError } from "@/shared/lib/KeepApiError"; export const fetcher = async ( url: string, @@ -17,7 +17,7 @@ export const fetcher = async ( // if the response has detail field, throw the detail field if (response.headers.get("content-type")?.includes("application/json")) { const data = await response.json(); - if(response.status === 401) { + if (response.status === 401) { throw new KeepApiError( `${data.message || data.detail}`, url, diff --git a/keep-ui/utils/hooks/useIncidents.ts b/keep-ui/utils/hooks/useIncidents.ts index 4c4bb3bb4..09c1f91cf 100644 --- a/keep-ui/utils/hooks/useIncidents.ts +++ b/keep-ui/utils/hooks/useIncidents.ts @@ -63,7 +63,9 @@ export const useIncidents = ( return { ...swrValue, - isLoading: swrValue.isLoading || sessionStatus === "loading", + isLoading: + swrValue.isLoading || + (!options.fallbackData && sessionStatus === "loading"), }; };