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(provider): vector dev #2720

Merged
merged 11 commits into from
Dec 12, 2024
10 changes: 7 additions & 3 deletions keep-ui/app/(keep)/providers/provider-tile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,12 @@ export default function ProviderTile({ provider, onClick }: Props) {
"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.coming_soon ? " opacity-50 cursor-not-allowed" : "")
(!provider.linked
? " cursor-pointer hover:shadow-lg"
: " cursor-auto") +
(provider.coming_soon && !provider.linked
? " opacity-50 cursor-not-allowed"
: "")
}
onClick={provider.coming_soon ? undefined : onClick}
disabled={provider.coming_soon}
Expand Down Expand Up @@ -219,7 +223,7 @@ export default function ProviderTile({ provider, onClick }: Props) {
<div>
<Title className="capitalize" title={provider.details?.name}>
{provider.display_name}{" "}
{provider.coming_soon && (
{provider.coming_soon && !provider.linked && (
<span className="text-sm">(Coming Soon)</span>
)}
</Title>
Expand Down
16 changes: 9 additions & 7 deletions keep-ui/app/(keep)/providers/providers-tiles.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import { Icon, Title } from "@tremor/react";
import { Title } from "@tremor/react";
import { Providers, Provider } from "./providers";
import { useEffect, useState } from "react";
// TODO: replace with custom component, package is not updated for last 4 years
Expand All @@ -9,6 +9,7 @@ import ProviderTile from "./provider-tile";
import "react-sliding-side-panel/lib/index.css";
import { useSearchParams } from "next/navigation";
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline";
import { Tooltip } from "@/shared/ui";

const ProvidersTiles = ({
providers,
Expand Down Expand Up @@ -95,12 +96,13 @@ const ProvidersTiles = ({
<Title>{getSectionTitle()}</Title>
{linkedProvidersMode && (
<div className="relative">
<Icon
icon={QuestionMarkCircleIcon} // Use the appropriate icon for your use case
className="text-gray-400 hover:text-gray-600"
size="sm"
tooltip="Providers that send alerts to Keep and are not installed."
/>
<Tooltip
content={
<>Providers that send alerts to Keep and are not installed.</>
}
>
<QuestionMarkCircleIcon className="w-4 h-4" />
</Tooltip>
</div>
)}
</div>
Expand Down
Binary file added keep-ui/public/icons/vectordev-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 0 additions & 13 deletions keep/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
)
from keep.api.routes.auth import groups as auth_groups
from keep.api.routes.auth import permissions, roles, users
from keep.api.routes.dashboard import provision_dashboards
from keep.event_subscriber.event_subscriber import EventSubscriber
from keep.identitymanager.identitymanagerfactory import (
IdentityManagerFactory,
Expand All @@ -61,9 +60,7 @@

# load all providers into cache
from keep.providers.providers_factory import ProvidersFactory
from keep.providers.providers_service import ProvidersService
from keep.workflowmanager.workflowmanager import WorkflowManager
from keep.workflowmanager.workflowstore import WorkflowStore

load_dotenv(find_dotenv())
keep.api.logging.setup_logging()
Expand All @@ -75,7 +72,6 @@
CONSUMER = os.environ.get("CONSUMER", "true") == "true"

AUTH_TYPE = os.environ.get("AUTH_TYPE", IdentityManagerTypes.NOAUTH.value).lower()
PROVISION_RESOURCES = os.environ.get("PROVISION_RESOURCES", "true") == "true"
try:
KEEP_VERSION = metadata.version("keep")
except Exception:
Expand Down Expand Up @@ -185,15 +181,6 @@ async def root():
async def on_startup():
logger.info("Loading providers into cache")
ProvidersFactory.get_all_providers()
if PROVISION_RESOURCES:
# provision providers from env. relevant only on single tenant.
logger.info("Provisioning providers and workflows")
ProvidersService.provision_providers_from_env(SINGLE_TENANT_UUID)
logger.info("Providers loaded successfully")
WorkflowStore.provision_workflows_from_directory(SINGLE_TENANT_UUID)
logger.info("Workflows provisioned successfully")
provision_dashboards(SINGLE_TENANT_UUID)
logger.info("Dashboards provisioned successfully")
# Start the services
logger.info("Starting the services")
# Start the scheduler
Expand Down
20 changes: 20 additions & 0 deletions keep/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,40 @@
from keep.api.api import AUTH_TYPE
from keep.api.core.db_on_start import migrate_db, try_create_single_tenant
from keep.api.core.dependencies import SINGLE_TENANT_UUID
from keep.api.routes.dashboard import provision_dashboards
from keep.identitymanager.identitymanagerfactory import IdentityManagerTypes
from keep.providers.providers_factory import ProvidersFactory
from keep.providers.providers_service import ProvidersService
from keep.workflowmanager.workflowstore import WorkflowStore

PORT = int(os.environ.get("PORT", 8080))
PROVISION_RESOURCES = os.environ.get("PROVISION_RESOURCES", "true") == "true"

keep.api.logging.setup_logging()
logger = logging.getLogger(__name__)


def provision_resources():
if PROVISION_RESOURCES:
# provision providers from env. relevant only on single tenant.
logger.info("Provisioning providers and workflows")
ProvidersService.provision_providers_from_env(SINGLE_TENANT_UUID)
logger.info("Providers loaded successfully")
WorkflowStore.provision_workflows_from_directory(SINGLE_TENANT_UUID)
logger.info("Workflows provisioned successfully")
provision_dashboards(SINGLE_TENANT_UUID)
logger.info("Dashboards provisioned successfully")
else:
logger.info("Provisioning resources is disabled")


def on_starting(server=None):
"""This function is called by the gunicorn server when it starts"""
logger.info("Keep server starting")

migrate_db()
provision_resources()

# Load this early and use preloading
# https://www.joelsleppy.com/blog/gunicorn-application-preloading/
# @tb: 👏 @Matvey-Kuk
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ def downgrade() -> None:
existing_nullable=True,
)
batch_op.alter_column(
"settings", existing_type=sa.JSON(), type_=sa.VARCHAR(length=255), nullable=False
"settings",
existing_type=sa.JSON(),
type_=sa.VARCHAR(length=255),
nullable=False,
)

# ### end Alembic commands ###
1 change: 1 addition & 0 deletions keep/api/tasks/process_event_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ def process_event(
provider_type is not None
and isinstance(event, dict)
or isinstance(event, FormData)
or isinstance(event, list)
):
try:
provider_class = ProvidersFactory.get_provider_class(provider_type)
Expand Down
4 changes: 2 additions & 2 deletions keep/providers/base/base_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ def query(self, **kwargs: dict):

@staticmethod
def _format_alert(
event: dict, provider_instance: "BaseProvider" = None
event: dict | list[dict], provider_instance: "BaseProvider" = None
) -> AlertDto | list[AlertDto]:
"""
Format an incoming alert.
Expand All @@ -335,7 +335,7 @@ def _format_alert(
@classmethod
def format_alert(
cls,
event: dict,
event: dict | list[dict],
tenant_id: str | None,
provider_type: str | None,
provider_id: str | None,
Expand Down
Empty file.
64 changes: 64 additions & 0 deletions keep/providers/vectordev_provider/vectordev_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import dataclasses
import json

import pydantic

from keep.api.models.alert import AlertDto
from keep.contextmanager.contextmanager import ContextManager
from keep.providers.base.base_provider import BaseProvider
from keep.providers.models.provider_config import ProviderConfig


@pydantic.dataclasses.dataclass
class VectordevProviderAuthConfig:
api_key: str = dataclasses.field(
metadata={"required": True, "description": "API key", "sensitive": True}
)


class VectordevProvider(BaseProvider):
PROVIDER_DISPLAY_NAME = "Vector"
PROVIDER_CATEGORY = ["Monitoring", "Developer Tools"]
PROVIDER_COMING_SOON = True

def __init__(
self, context_manager: ContextManager, provider_id: str, config: ProviderConfig
):
super().__init__(context_manager, provider_id, config)

def validate_config(self):
self.authentication_config = VectordevProviderAuthConfig(
**self.config.authentication
)

def _format_alert(
event: list[dict], provider_instance: "BaseProvider" = None
) -> AlertDto | list[AlertDto]:
events = []
# event is a list of events
for e in event:
event_json = None
try:
event_json = json.loads(e.get("message"))
except json.JSONDecodeError:
pass

events.append(
AlertDto(
name="",
host=e.get("host"),
message=e.get("message"),
description=e.get("message"),
lastReceived=e.get("timestamp"),
source_type=e.get("source_type"),
source=["vectordev"],
original_event=event_json,
)
)
return events

def dispose(self):
"""
No need to dispose of anything, so just do nothing.
"""
pass
5 changes: 5 additions & 0 deletions tests/fixtures/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,14 @@ def test_app(monkeypatch, request):
if "keep.api.api" in sys.modules:
importlib.reload(sys.modules["keep.api.api"])

if "keep.api.config" in sys.modules:
importlib.reload(sys.modules["keep.api.config"])

# Import and return the app instance
from keep.api.api import get_app
from keep.api.config import provision_resources

provision_resources()
app = get_app()

# Manually trigger the startup event
Expand Down
15 changes: 15 additions & 0 deletions tests/test_provisioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ def test_reprovision_workflow(monkeypatch, db_session, client, test_app):
for event_handler in app.router.on_startup:
asyncio.run(event_handler())

# manually trigger the provision resources
from keep.api.config import provision_resources

provision_resources()

client = TestClient(get_app())

response = client.get("/workflows", headers={"x-api-key": "someapikey"})
Expand Down Expand Up @@ -210,6 +215,11 @@ def test_reprovision_provider(monkeypatch, db_session, client, test_app):
for event_handler in app.router.on_startup:
asyncio.run(event_handler())

# manually trigger the provision resources
from keep.api.config import provision_resources

provision_resources()

client = TestClient(app)

# Step 3: Verify if the new provider is provisioned after reloading
Expand Down Expand Up @@ -293,6 +303,11 @@ def test_reprovision_dashboard(monkeypatch, db_session, client, test_app):
for event_handler in app.router.on_startup:
asyncio.run(event_handler())

# manually trigger the provision resources
from keep.api.config import provision_resources

provision_resources()

client = TestClient(app)

# Step 3: Verify if the new dashboard is provisioned after reloading
Expand Down
Loading