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: slightly different UX for provider tiles without hiding content on hover #1736

Merged
merged 3 commits into from
Aug 27, 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
166 changes: 84 additions & 82 deletions keep-ui/app/providers/provider-tile.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import {
Badge,
Button,
Icon,
SparkAreaChart,
Subtitle,
Text,
Title,
} from "@tremor/react";
import {Provider, TProviderLabels} from "./providers";
import { Provider, TProviderLabels } from "./providers";
import {
BellAlertIcon,
ChatBubbleBottomCenterIcon,
CircleStackIcon,
QueueListIcon,
TicketIcon,
MapIcon,
Cog6ToothIcon,
} from "@heroicons/react/20/solid";
import "./provider-tile.css";
import moment from "moment";
Expand Down Expand Up @@ -88,8 +88,7 @@ const getEmptyDistribution = () => {
return emptyDistribution;
};


function getIconForTag(tag:TProviderLabels) {
function getIconForTag(tag: TProviderLabels) {
switch (tag) {
case "alert":
return BellAlertIcon;
Expand All @@ -111,25 +110,59 @@ export default function ProviderTile({ provider, onClick }: Props) {
if (provider.installed || provider.linked) {
return null;
}
return (<div className="labels flex flex-wrap group-hover:hidden gap-1">
{provider.tags.map((tag) => {
return (
<Badge
key={tag}
icon={getIconForTag(tag)}
size="xs"
color="slate"
>
<p>{tag}</p>
</Badge>
);
})}
</div>)
}
return (
<div className="labels flex flex-wrap gap-1">
{provider.tags.map((tag) => {
return (
<Badge key={tag} icon={getIconForTag(tag)} size="xs" color="slate">
<p>{tag}</p>
</Badge>
);
})}
</div>
);
};

const renderChart = () => {
const className = "mt-2 h-8 w-20 sm:h-10 sm:w-full";
if (!provider.installed && !provider.linked) {
return null;
}

if (provider.alertsDistribution && provider.alertsDistribution.length > 0) {
return (
<SparkAreaChart
data={addOneToDistribution(provider.alertsDistribution)}
categories={["number"]}
index={"hour"}
colors={["orange"]}
showGradient={true}
autoMinValue={true}
className={className}
/>
);
}

return (
<SparkAreaChart
data={getEmptyDistribution()}
categories={["number"]}
index={"hour"}
colors={["orange"]}
className={className}
autoMinValue={true}
maxValue={1}
/>
);
};

return (
<div
className="tile-basis py-2 px-4 relative group flex justify-around items-center bg-white rounded-lg shadow h-44 hover:shadow-lg hover:grayscale-0 cursor-pointer gap-2"
<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:shadow-lg hover:grayscale-0 cursor-pointer gap-3" +
// Add fixed height only if provider card doesn't have much content
(!provider.installed && !provider.linked ? " h-32" : "")
}
onClick={onClick}
>
<div className="flex-1 min-w-0">
Expand Down Expand Up @@ -158,7 +191,7 @@ export default function ProviderTile({ provider, onClick }: Props) {
/>
)}
{provider.installed ? (
<Text color={"green"} className="flex text-xs group-hover:hidden">
<Text color={"green"} className="flex text-xs">
Connected
</Text>
) : null}
Expand All @@ -169,24 +202,17 @@ export default function ProviderTile({ provider, onClick }: Props) {
) : null}
<div className="flex flex-col gap-2">
<div>
<Title
className={`${
!provider.linked ? "group-hover:hidden" : ""
} capitalize`}
title={provider.details?.name}
>
<Title className="capitalize" title={provider.details?.name}>
{provider.display_name}{" "}
</Title>

{provider.details && provider.details.name && (
<Subtitle className="group-hover:hidden">
<Subtitle className="truncate">
id: {provider.details.name}
</Subtitle>
)}
{provider.last_alert_received ? (
<Text
className={`${!provider.linked ? "group-hover:hidden" : ""}`}
>
<Text>
Last alert: {moment(provider.last_alert_received).fromNow()}
</Text>
) : (
Expand All @@ -195,59 +221,35 @@ export default function ProviderTile({ provider, onClick }: Props) {
{provider.linked && provider.id ? (
<Text className="truncate">Id: {provider.id}</Text>
) : null}
{(provider.installed || provider.linked) &&
provider.alertsDistribution &&
provider.alertsDistribution.length > 0 ? (
<SparkAreaChart
data={addOneToDistribution(provider.alertsDistribution)}
categories={["number"]}
index={"hour"}
colors={["orange"]}
showGradient={true}
autoMinValue={true}
className={`${
!provider.linked ? "group-hover:hidden" : ""
} mt-2 h-8 w-20 sm:h-10 sm:w-36`}
/>
) : provider.installed || provider.linked ? (
<SparkAreaChart
data={getEmptyDistribution()}
categories={["number"]}
index={"hour"}
colors={["orange"]}
className={`${
!provider.linked ? "group-hover:hidden" : ""
} mt-2 h-8 w-20 sm:h-10 sm:w-36`}
autoMinValue={true}
maxValue={1}
/>
) : null}
{renderChart()}
</div>
{renderTags()}
{!provider.linked && (
<Button
variant="secondary"
size="xs"
color={provider.installed ? "orange" : "green"}
className="hidden group-hover:block pd-2"
>
{provider.installed ? "Modify" : "Connect"}
</Button>
)}
</div>
</div>
<ImageWithFallback
src={`/icons/${provider.type}-icon.png`}
fallbackSrc={`/icons/keep-icon.png`}
width={48}
height={48}
alt={provider.type}
className={`${
provider.installed || provider.linked
? ""
: "grayscale group-hover:grayscale-0"
}`}
/>
</div>
<div className="flex flex-col justify-center h-full">
<div className="flex-grow flex items-center">
<ImageWithFallback
src={`/icons/${provider.type}-icon.png`}
fallbackSrc={`/icons/keep-icon.png`}
width={48}
height={48}
alt={provider.type}
className={`${
provider.installed || provider.linked
? ""
: "grayscale group-hover:grayscale-0"
}`}
/>
</div>
{provider.installed ? (
<Icon
icon={Cog6ToothIcon}
color="gray"
className="w-6 h-6 self-end place-self-end"
tooltip="Modify"
/>
) : null}
</div>
</button>
);
}
52 changes: 28 additions & 24 deletions keep-ui/app/providers/providers-tiles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,31 @@ const ProvidersTiles = ({
if (isConnected) handleCloseModal();
};

const getSectionTitle = () => {
if (installedProvidersMode) {
return "Installed Providers";
}

if (linkedProvidersMode) {
return "Linked Providers";
}

return "Connect Provider";
};

const sortedProviders = providers.sort(
(a, b) =>
Number(b.can_setup_webhook) - Number(a.can_setup_webhook) ||
Number(b.supports_webhook) - Number(a.supports_webhook) ||
Number(b.oauth2_url ? true : false) - Number(a.oauth2_url ? true : false)
);

return (
<div>
<div className="flex items-center mb-2.5">
<Title>
{installedProvidersMode
? "Installed Providers"
: linkedProvidersMode
? "Linked Providers"
: "Available Providers"}
</Title>
<Title>{getSectionTitle()}</Title>
{linkedProvidersMode && (
<div className="ml-2 relative">
<div className="relative">
<Icon
icon={QuestionMarkCircleIcon} // Use the appropriate icon for your use case
className="text-gray-400 hover:text-gray-600"
Expand All @@ -121,22 +134,13 @@ const ProvidersTiles = ({
</div>

<div className="flex flex-wrap mb-5 gap-5">
{providers
.filter(provider => Object.keys(provider.config || {}).length > 0 || (provider.tags && provider.tags.includes('alert')))
.sort(
(a, b) =>
Number(b.can_setup_webhook) - Number(a.can_setup_webhook) ||
Number(b.supports_webhook) - Number(a.supports_webhook) ||
Number(b.oauth2_url ? true : false) -
Number(a.oauth2_url ? true : false)
)
.map((provider) => (
<ProviderTile
key={provider.id}
provider={provider}
onClick={() => handleConnectProvider(provider)}
></ProviderTile>
))}
{sortedProviders.map((provider) => (
<ProviderTile
key={provider.id}
provider={provider}
onClick={() => handleConnectProvider(provider)}
></ProviderTile>
))}
</div>

<SlidingPanel
Expand Down
18 changes: 9 additions & 9 deletions tests/e2e_tests/test_end_to_end.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ def test_providers_page_is_accessible(browser):
)
browser.goto("http://localhost:3000/providers")
# get the GCP Monitoring provider
browser.locator("div").filter(
has_text=re.compile(r"^GCP Monitoring alertConnect$")
browser.locator("button").filter(
has_text=re.compile(r"^GCP Monitoring alert$")
).first.click()
browser.get_by_role("button", name="Cancel").click()
# connect resend provider
browser.locator("div").filter(
has_text=re.compile(r"^resend messagingConnect$")
browser.locator("button").filter(
has_text=re.compile(r"^resend messaging$")
).first.click()
browser.get_by_placeholder("Enter provider name").click()
random_provider_name = "".join(
Expand All @@ -106,14 +106,14 @@ def test_providers_page_is_accessible(browser):
browser.get_by_placeholder("Enter provider name").fill(random_provider_name)
browser.get_by_placeholder("Enter provider name").press("Tab")
browser.get_by_placeholder("Enter api_key").fill("bla")
browser.get_by_role("button", name="Connect").click()
browser.get_by_role("button", name="Connect", exact=True).click()
# wait a bit
browser.wait_for_selector("text=Connected", timeout=15000)
# make sure the provider is connected:
# find connected provider id label
id_label = browser.get_by_text(f"resend id: {random_provider_name}")
# click on parent div, the tile
id_label.locator('..').click()
# find and click the button containing the provider id in its nested elements
provider_button = browser.locator(f"button:has-text('{random_provider_name}')")
print(provider_button)
provider_button.click()
except Exception:
# Current file + test name for unique html and png dump.
current_test_name = (
Expand Down
18 changes: 9 additions & 9 deletions tests/e2e_tests/test_pushing_prometheus_alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ def test_pulling_prometheus_alerts_to_provider(browser):
browser.get_by_placeholder("Filter providers...").click()
browser.get_by_placeholder("Filter providers...").fill("prometheus")
browser.get_by_placeholder("Filter providers...").press("Enter")
browser.get_by_text("Available Providers").hover()
browser.locator("div").filter(
has_text=re.compile(r"^prometheus dataalertConnect$")
).nth(1).hover()
browser.get_by_text("Connect Provider").hover()
browser.locator("button").filter(
has_text=re.compile(r"^prometheus dataalert$")
).first.hover()

browser.get_by_role("button", name="Connect").click()
browser.locator("button:has-text('prometheus')").click()
browser.get_by_placeholder("Enter provider name").click()
browser.get_by_placeholder("Enter provider name").fill(provider_name)
browser.get_by_placeholder("Enter url").click()
Expand All @@ -55,11 +55,11 @@ def test_pulling_prometheus_alerts_to_provider(browser):
)

browser.mouse.wheel(1000, 10000) # Scroll down.
browser.get_by_role("button", name="Connect").click()
browser.get_by_role("button", name="Connect", exact=True).click()

# Validate provider is created
expect(
browser.locator("div")
browser.locator("button")
.filter(has_text=re.compile(re.escape(provider_name)))
.first
).to_be_visible()
Expand Down Expand Up @@ -92,7 +92,7 @@ def test_pulling_prometheus_alerts_to_provider(browser):

# Delete provider
browser.get_by_role("link", name="Providers").click()
browser.locator("div").filter(
browser.locator("button").filter(
has_text=re.compile(re.escape(provider_name))
).first.hover()
browser.locator(".tile-basis").first.click()
Expand All @@ -101,7 +101,7 @@ def test_pulling_prometheus_alerts_to_provider(browser):

# Assert provider was deleted
expect(
browser.locator("div")
browser.locator("button")
.filter(has_text=re.compile(re.escape(provider_name)))
.first
).not_to_be_visible()
Expand Down
Loading