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: Re-design probe selection when creating / editing checks #973

Merged
merged 21 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"react-popper": "^2.2.5",
"react-router-dom": "^5.2.0",
"rxjs": "7.8.1",
"usehooks-ts": "^3.1.0",
"valid-url": "^1.0.9",
"yaml": "^2.2.2",
"zod": "3.23.6"
Expand Down
125 changes: 0 additions & 125 deletions src/components/CheckEditor/CheckProbes.tsx

This file was deleted.

78 changes: 78 additions & 0 deletions src/components/CheckEditor/CheckProbes/CheckProbes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { forwardRef, useMemo, useState } from 'react';
import { Field, Stack } from '@grafana/ui';

import { Probe } from 'types';

import { PrivateProbesAlert } from './PrivateProbesAlert';
import { PROBES_FILTER_ID, ProbesFilter } from './ProbesFilter';
import { ProbesList } from './ProbesList';

interface CheckProbesProps {
probes: number[];
availableProbes: Probe[];
disabled?: boolean;
onChange: (probes: number[]) => void;
onBlur?: () => void;
invalid?: boolean;
error?: string;
}
export const CheckProbes = forwardRef(({ probes, availableProbes, onChange, error }: CheckProbesProps) => {
const [filteredProbes, setFilteredProbes] = useState<Probe[]>(availableProbes);

const publicProbes = useMemo(() => filteredProbes.filter((probe) => probe.public), [filteredProbes]);
const privateProbes = useMemo(() => filteredProbes.filter((probe) => !probe.public), [filteredProbes]);

const groupedByRegion = useMemo(
() =>
publicProbes.reduce((acc: Record<string, Probe[]>, curr: Probe) => {
const region = curr.region;
if (!acc[region]) {
acc[region] = [];
}
acc[region].push(curr);
return acc;
}, {}),
[publicProbes]
);

const showPrivateProbesDiscovery = privateProbes.length === 0 && filteredProbes.length === availableProbes.length;

return (
<div>
<Field
label="Probe locations"
description="Select one, multiple, or all probes where this target will be checked from. Deprecated probes can be removed, but they cannot be added."
invalid={!!error}
error={error}
htmlFor={PROBES_FILTER_ID}
>
<div>
<ProbesFilter probes={availableProbes} onSearch={setFilteredProbes} />
<Stack wrap="wrap">
{privateProbes.length > 0 && (
<ProbesList
title="Private probes"
probes={privateProbes}
selectedProbes={probes}
onSelectionChange={onChange}
/>
)}

{Object.entries(groupedByRegion).map(([region, allProbes]) => (
<ProbesList
key={region}
title={region}
probes={allProbes}
selectedProbes={probes}
onSelectionChange={onChange}
/>
))}
</Stack>
</div>
</Field>
{showPrivateProbesDiscovery && <PrivateProbesAlert />}
</div>
);
});

CheckProbes.displayName = 'CheckProbes';
40 changes: 40 additions & 0 deletions src/components/CheckEditor/CheckProbes/PrivateProbesAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { Alert, LinkButton, Stack, TextLink } from '@grafana/ui';
import { useLocalStorage } from 'usehooks-ts';

import { ROUTES } from 'types';
import { getRoute } from 'components/Routing.utils';

export const PrivateProbesAlert = () => {
const [dismissed, setDismissed] = useLocalStorage<boolean>('dismissedPrivateProbesAlert', false);

if (dismissed) {
return null;
}

return (
<Alert
title="You haven't set up any private probes yet."
severity="info"
Copy link
Contributor Author

@VikaCep VikaCep Oct 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original design used a different color and icon for this message, but with the intention of reusing and not modifying the elements we get from grafana/ui for consistency with other parts of the app, I've used the Alert component with info severity which displays in light blue. Happy to adapt it to the original design if you think that's better though.

image

image
(edit: changed content text after Chris comments)

onRemove={() => {
setDismissed(true);
}}
>
<Stack gap={1} direction="column" alignItems="flex-start">
<p>
Private probes are instances of the open source Grafana{' '}
<TextLink
href="https://grafana.com/docs/grafana-cloud/testing/synthetic-monitoring/set-up/set-up-private-probes/"
external={true}
>
Synthetic Monitoring Agent
</TextLink>{' '}
and are only accessible to you.
</p>
<LinkButton size="sm" href={`${getRoute(ROUTES.NewProbe)}`}>
Set up a Private Probe
</LinkButton>
</Stack>
</Alert>
);
};
41 changes: 41 additions & 0 deletions src/components/CheckEditor/CheckProbes/ProbesFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { useState } from 'react';

import { Probe } from 'types';
import { SearchFilter } from 'components/SearchFilter';

export const PROBES_FILTER_ID = 'check-probes-filter';

export const ProbesFilter = ({ probes, onSearch }: { probes: Probe[]; onSearch: (probes: Probe[]) => void }) => {
const [showEmptyState, setShowEmptyState] = useState(false);
const [filterText, setFilterText] = useState('');

const handleSearch = (searchValue: string) => {
setFilterText(searchValue);
const filteredProbes = probes.filter(
(probe) =>
probe.region.toLowerCase().includes(searchValue) ||
probe.name.toLowerCase().includes(searchValue) ||
probe.longRegion?.toLowerCase().includes(searchValue) ||
probe.city?.toLowerCase().includes(searchValue) ||
probe.provider?.toLowerCase().includes(searchValue) ||
probe.country?.toLowerCase().includes(searchValue) ||
probe.countryCode?.toLowerCase().includes(searchValue)
);

onSearch(filteredProbes);
setShowEmptyState(filteredProbes.length === 0);
};

return (
<>
<SearchFilter
onSearch={handleSearch}
id={PROBES_FILTER_ID}
value={filterText}
showEmptyState={showEmptyState}
emptyText="There are no probes matching your criteria."
placeholder="Find a probe by city, country, region or provider"
/>
</>
);
};
Loading
Loading