Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make providers page easier to navigate #2610

Merged
merged 6 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ProvidersCategories } from "./providers-categories";
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { TProviderCategory } from "@/app/(keep)/providers/providers";
import { Badge } from "@tremor/react";
import { useFilterContext } from "../../filter-context";

export const ProvidersCategories = () => {
const { providersSelectedCategories, setProvidersSelectedCategories } =
useFilterContext();

const categories: TProviderCategory[] = [
"Monitoring",
"Incident Management",
"Cloud Infrastructure",
"Ticketing",
"Developer Tools",
"Database",
"Identity and Access Management",
"Security",
"Collaboration",
"CRM",
"Queues",
"Coming Soon",
"Others",
];

const toggleCategory = (category: TProviderCategory) => {
setProvidersSelectedCategories((prev) =>
prev.includes(category)
? prev.filter((c) => c !== category)
: [...prev, category]
);
};

return (
<div className="w-full flex flex-wrap justify-start gap-2 mt-2.5">
{categories.map((category) => (
<Badge
color={
providersSelectedCategories.includes(category) ? "orange" : "slate"
}
className={`rounded-full ${
providersSelectedCategories.includes(category)
? "shadow-inner"
: "hover:shadow-inner"
} cursor-pointer`}
key={category}
onClick={() => toggleCategory(category)}
>
{category}
</Badge>
))}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const ProvidersFilterByLabel: FC = (props) => {
<MultiSelect
onValueChange={headerSelect}
value={providersSelectedTags}
placeholder="Filter by label..."
className="w-full ml-2.5"
placeholder="All Labels"
className="w-64 ml-2.5"
icon={TagIcon}
>
{options.map(([value, label]) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const ProvidersSearch: FC = () => {
id="search-providers"
icon={MagnifyingGlassIcon}
placeholder="Filter providers..."
className="w-full"
value={providersSearchString}
onChange={handleChange}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createContext, useState, FC, PropsWithChildren } from "react";
import { IFilterContext } from "./types";
import { useSearchParams } from "next/navigation";
import { PROVIDER_LABELS_KEYS } from "./constants";
import type { TProviderLabels } from "../providers";
import type { TProviderCategory, TProviderLabels } from "../providers";

export const FilterContext = createContext<IFilterContext | null>(null);

Expand All @@ -12,6 +12,9 @@ export const FilerContextProvider: FC<PropsWithChildren> = ({ children }) => {
const [providersSearchString, setProvidersSearchString] =
useState<string>("");

const [providersSelectedCategories, setProvidersSelectedCategories] =
useState<TProviderCategory[]>([]);

const [providersSelectedTags, setProvidersSelectedTags] = useState<
TProviderLabels[]
>(() => {
Expand All @@ -26,8 +29,10 @@ export const FilerContextProvider: FC<PropsWithChildren> = ({ children }) => {
const contextValue: IFilterContext = {
providersSearchString,
providersSelectedTags,
providersSelectedCategories,
setProvidersSelectedTags,
setProvidersSearchString,
setProvidersSelectedCategories,
};

return (
Expand Down
4 changes: 3 additions & 1 deletion keep-ui/app/(keep)/providers/filter-context/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Dispatch, SetStateAction } from "react";
import { TProviderLabels } from "../providers";
import { TProviderCategory, TProviderLabels } from "../providers";

export interface IFilterContext {
providersSearchString: string;
providersSelectedTags: TProviderLabels[];
providersSelectedCategories: TProviderCategory[];
setProvidersSearchString: Dispatch<SetStateAction<string>>;
setProvidersSelectedTags: Dispatch<SetStateAction<TProviderLabels[]>>;
setProvidersSelectedCategories: Dispatch<SetStateAction<TProviderCategory[]>>;
}
6 changes: 4 additions & 2 deletions keep-ui/app/(keep)/providers/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ import { PropsWithChildren } from "react";
import { ProvidersFilterByLabel } from "./components/providers-filter-by-label";
import { ProvidersSearch } from "./components/providers-search";
import { FilerContextProvider } from "./filter-context";
import { ProvidersCategories } from "./components/providers-categories";

export default function ProvidersLayout({ children }: PropsWithChildren) {
return (
<FilerContextProvider>
<main className="p-4">
<div className="flex w-full justify-end mb-4 ml-2.5">
<div className="flex">
<div className="flex w-full flex-col items-center mb-4">
<div className="flex w-full">
<ProvidersSearch />
<ProvidersFilterByLabel />
</div>
<ProvidersCategories />
</div>
<div className="flex flex-col">{children}</div>
</main>
Expand Down
26 changes: 24 additions & 2 deletions keep-ui/app/(keep)/providers/page.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ export default function ProvidersPage({
session,
isLocalhost,
} = useFetchProviders();
const { providersSearchString, providersSelectedTags } = useFilterContext();
const {
providersSearchString,
providersSelectedTags,
providersSelectedCategories,
} = useFilterContext();
const apiUrl = useApiUrl();
const router = useRouter();
useEffect(() => {
Expand Down Expand Up @@ -147,6 +151,21 @@ export default function ProvidersPage({
);
};

const searchCategories = (provider: Provider) => {
if (providersSelectedCategories.includes("Coming Soon")) {
if (provider.coming_soon) {
return true;
}
}

return (
providersSelectedCategories.length === 0 ||
provider.categories.some((category) =>
providersSelectedCategories.includes(category)
)
);
};

const searchTags = (provider: Provider) => {
return (
providersSelectedTags.length === 0 ||
Expand All @@ -171,7 +190,10 @@ export default function ProvidersPage({
)}
<ProvidersTiles
providers={providers.filter(
(provider) => searchProviders(provider) && searchTags(provider)
(provider) =>
searchProviders(provider) &&
searchTags(provider) &&
searchCategories(provider)
)}
isLocalhost={isLocalhost}
/>
Expand Down
14 changes: 9 additions & 5 deletions keep-ui/app/(keep)/providers/provider-tile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,16 +159,17 @@ export default function ProviderTile({ provider, onClick }: Props) {
/>
);
};

return (
<button
className={
"tile-basis text-left min-w-0 py-4 px-4 relative group flex justify-around items-center bg-white rounded-lg shadow hover:grayscale-0 gap-3" +
"min-h-36 tile-basis text-left min-w-0 py-4 px-4 relative group flex justify-around items-center bg-white rounded-lg shadow hover:grayscale-0 gap-3" +
// Add fixed height only if provider card doesn't have much content
(!provider.installed && !provider.linked ? " h-32" : "") +
(!provider.linked ? "cursor-pointer hover:shadow-lg" : "")
(!provider.linked ? "cursor-pointer hover:shadow-lg" : "") +
(provider.coming_soon ? " opacity-50 cursor-not-allowed" : "")
}
onClick={onClick}
onClick={provider.coming_soon ? undefined : onClick}
disabled={provider.coming_soon}
>
<div className="flex-1 min-w-0">
{(provider.can_setup_webhook || provider.supports_webhook) &&
Expand Down Expand Up @@ -218,6 +219,9 @@ export default function ProviderTile({ provider, onClick }: Props) {
<div>
<Title className="capitalize" title={provider.details?.name}>
{provider.display_name}{" "}
{provider.coming_soon && (
<span className="text-sm">(Coming Soon)</span>
)}
</Title>

{provider.details && provider.details.name && (
Expand Down Expand Up @@ -249,7 +253,7 @@ export default function ProviderTile({ provider, onClick }: Props) {
height={48}
alt={provider.type}
className={`${
provider.installed || provider.linked
provider.installed || provider.linked || provider.coming_soon
? ""
: "grayscale group-hover:grayscale-0"
}`}
Expand Down
7 changes: 5 additions & 2 deletions keep-ui/app/(keep)/providers/providers-tiles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const ProvidersTiles = ({
return "Linked Providers";
}

return "Connect Provider";
return "Available Providers";
};

const sortedProviders = providers
Expand All @@ -115,6 +115,9 @@ const ProvidersTiles = ({
)
.sort(
(a, b) =>
// Put coming_soon providers at the end
Number(a.coming_soon) - Number(b.coming_soon) ||
// Then sort by webhook/oauth capabilities
Number(b.can_setup_webhook) - Number(a.can_setup_webhook) ||
Number(b.supports_webhook) - Number(a.supports_webhook) ||
Number(b.oauth2_url ? true : false) -
Expand All @@ -131,7 +134,7 @@ const ProvidersTiles = ({
icon={QuestionMarkCircleIcon} // Use the appropriate icon for your use case
className="text-gray-400 hover:text-gray-600"
size="sm"
tooltip="Providers which send alerts without being installed by Keep"
tooltip="Providers that send alerts to Keep and are not installed."
/>
</div>
)}
Expand Down
20 changes: 19 additions & 1 deletion keep-ui/app/(keep)/providers/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ interface AlertDistritbuionData {
number: number;
}

export type TProviderCategory =
| "Monitoring"
| "Incident Management"
| "Cloud Infrastructure"
| "Ticketing"
| "Developer Tools"
| "Database"
| "Identity and Access Management"
| "Security"
| "Collaboration"
| "CRM"
| "Queues"
| "Coming Soon"
| "Others";

export type TProviderLabels =
| "alert"
| "incident"
Expand Down Expand Up @@ -81,7 +96,6 @@ export interface Provider {
id: string;
// the name of the provider
display_name: string;
comingSoon?: boolean;
can_query: boolean;
query_params?: string[];
can_notify: boolean;
Expand All @@ -101,6 +115,8 @@ export interface Provider {
alertsDistribution?: AlertDistritbuionData[];
alertExample?: { [key: string]: string };
provisioned?: boolean;
categories: TProviderCategory[];
coming_soon: boolean;
}

export type Providers = Provider[];
Expand All @@ -119,4 +135,6 @@ export const defaultProvider: Provider = {
tags: [],
validatedScopes: {},
pulling_enabled: true,
categories: ["Others"],
coming_soon: false,
};
4 changes: 2 additions & 2 deletions keep-ui/app/(signin)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const metadata = {
title: "Next.js",
description: "Generated by Next.js",
title: "Keep",
description: "The open-source alert management and AIOps platform",
};

export default function RootLayout({
Expand Down
Binary file added keep-ui/public/icons/salesforce-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added keep-ui/public/icons/zendesk-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion keep/api/models/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ class Provider(BaseModel):
last_pull_time: datetime | None = None
docs: str | None = None
tags: list[
Literal["alert", "ticketing", "messaging", "data", "queue", "topology", "incident"]
Literal[
"alert", "ticketing", "messaging", "data", "queue", "topology", "incident"
]
] = []
categories: list[str] = ["Others"]
coming_soon: bool = False
alertsDistribution: dict[str, int] | None = None
alertExample: dict | None = None
default_fingerprint_fields: list[str] | None = None
Expand Down
1 change: 1 addition & 0 deletions keep/providers/aks_provider/aks_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class AksProvider(BaseProvider):
"""Enrich alerts using data from AKS."""

PROVIDER_DISPLAY_NAME = "Azure AKS"
PROVIDER_CATEGORY = ["Cloud Infrastructure"]

def __init__(
self, context_manager: ContextManager, provider_id: str, config: ProviderConfig
Expand Down
19 changes: 13 additions & 6 deletions keep/providers/appdynamics_provider/appdynamics_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class AppdynamicsProviderAuthConfig:
"""
AppDynamics authentication configuration.
"""

appDynamicsAccountName: str = dataclasses.field(
metadata={
"required": True,
Expand Down Expand Up @@ -87,18 +88,20 @@ def check_password_or_token(cls, values):
username, password, token = (
values.get("appDynamicsUsername"),
values.get("appDynamicsPassword"),
values.get("appDynamicsAccessToken")
values.get("appDynamicsAccessToken"),
)
if not (username and password) and not token:
raise ValueError("Either username/password or access token must be provided")
raise ValueError(
"Either username/password or access token must be provided"
)
return values


class AppdynamicsProvider(BaseProvider):
"""Install Webhooks and receive alerts from AppDynamics."""

PROVIDER_DISPLAY_NAME = "AppDynamics"

PROVIDER_CATEGORY = ["Monitoring"]
PROVIDER_SCOPES = [
ProviderScope(
name="authenticated",
Expand Down Expand Up @@ -196,7 +199,9 @@ def validate_scopes(self) -> dict[str, bool | str]:
administrator = "Missing Administrator Privileges"
self.logger.info("Validating AppDynamics Scopes")

user_id = self.get_user_id_by_name(self.authentication_config.appDynamicsAccountName)
user_id = self.get_user_id_by_name(
self.authentication_config.appDynamicsAccountName
)

url = self.__get_url(
paths=[
Expand Down Expand Up @@ -237,13 +242,15 @@ def __get_headers(self):
}

def __get_auth(self) -> tuple[str, str]:
if self.authentication_config.appDynamicsUsername and self.authentication_config.appDynamicsPassword:
if (
self.authentication_config.appDynamicsUsername
and self.authentication_config.appDynamicsPassword
):
return (
f"{self.authentication_config.appDynamicsUsername}@{self.authentication_config.appDynamicsAccountName}",
self.authentication_config.appDynamicsPassword,
)


def __create_http_response_template(self, keep_api_url: str, api_key: str):
keep_api_host, keep_api_path = keep_api_url.rsplit("/", 1)

Expand Down
Loading
Loading