Skip to content
Draft
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
159 changes: 126 additions & 33 deletions src/components/CheckEditor/CheckProbes/ProbesList.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React, { useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data';
import { Checkbox, Label, Stack, Text, TextLink, useStyles2 } from '@grafana/ui';
import { css } from '@emotion/css';

import { ProbeWithMetadata } from 'types';
import { CheckFormValues, FeatureName, ProbeWithMetadata } from 'types';
import { useCurrentK6Version } from 'data/useK6Channels';
import { useFeatureFlag } from 'hooks/useFeatureFlag';
import { DeprecationNotice } from 'components/DeprecationNotice/DeprecationNotice';
import { ProbeStatus } from 'components/ProbeCard/ProbeStatus';

Expand All @@ -21,6 +24,53 @@ export const ProbesList = ({
disabled?: boolean;
}) => {
const styles = useStyles2(getStyles);
const { getValues } = useFormContext<CheckFormValues>();
const { isEnabled: isVersionManagementEnabled } = useFeatureFlag(FeatureName.VersionManagement);

const { selectedChannel, isScriptedOrBrowser } = useMemo(() => {
const values = getValues();
const checkType = values.checkType;
const isScriptedOrBrowser = checkType === 'scripted' || checkType === 'browser';

let selectedChannel = '';
if (checkType === 'scripted' && values.settings?.scripted?.channel) {
selectedChannel = values.settings.scripted.channel;
} else if (checkType === 'browser' && values.settings?.browser?.channel) {
selectedChannel = values.settings.browser.channel;
}

return { selectedChannel, isScriptedOrBrowser };
}, [getValues]);

const { data: currentChannelVersion } = useCurrentK6Version(
isVersionManagementEnabled && isScriptedOrBrowser && Boolean(selectedChannel),
selectedChannel
);

const getProbeK6Version = (probe: ProbeWithMetadata) => {
if (!isVersionManagementEnabled || !isScriptedOrBrowser) {
return null; // Feature not enabled or not relevant for non-scripted/browser checks
}

// For legacy probes (no binary provisioning), show their static k6 version
if (!probe.supportsBinaryProvisioning && probe.k6Version) {
return probe.k6Version;
}

// For probes with binary provisioning, show the resolved channel version
if (probe.supportsBinaryProvisioning && currentChannelVersion) {
return currentChannelVersion;
}

return null;
};

const isProbeCompatible = (probe: ProbeWithMetadata): boolean => {
if (!isVersionManagementEnabled || !isScriptedOrBrowser || !selectedChannel) {
return true;
}
return probe.supportedChannels?.includes(selectedChannel) ?? false;
};

const handleToggleAll = () => {
if (allProbesSelected) {
Expand Down Expand Up @@ -76,40 +126,56 @@ export const ProbesList = ({
</Label>
</div>
<div className={styles.probesList}>
{probes.map((probe: ProbeWithMetadata) => (
<div key={probe.id} className={styles.item}>
<Checkbox
id={`probe-${probe.id}`}
onClick={() => handleToggleProbe(probe)}
checked={selectedProbes.includes(probe.id!)}
disabled={disabled}
/>
<Label htmlFor={`probe-${probe.id}`}>
<div className={styles.columnLabel}>
<ProbeStatus probe={probe} />{' '}
{`${probe.displayName}${probe.countryCode ? `, ${probe.countryCode}` : ''} ${
probe.provider ? `(${probe.provider})` : ''
}`}
{probe.deprecated && (
<DeprecationNotice
tooltipContent={
<div>
This probe is deprecated and will be removed soon. For more information{' '}
<TextLink
variant={'bodySmall'}
href="https://grafana.com/docs/grafana-cloud/whats-new/2025-01-14-launch-and-shutdown-dates-for-synthetics-probes-in-february-2025/"
external
>
click here.
</TextLink>
</div>
}
/>
{probes.map((probe: ProbeWithMetadata) => {
const isCompatible = isProbeCompatible(probe);
const isProbeDisabled = disabled || !isCompatible;
const k6Version = getProbeK6Version(probe);

return (
<div key={probe.id} className={`${styles.item} ${!isCompatible ? styles.incompatibleItem : ''}`}>
<Checkbox
id={`probe-${probe.id}`}
onClick={() => handleToggleProbe(probe)}
checked={selectedProbes.includes(probe.id!)}
disabled={isProbeDisabled}
/>
<div className={styles.probeContent}>
<Label htmlFor={`probe-${probe.id}`}>
<div className={`${styles.columnLabel} ${!isCompatible ? styles.incompatibleLabel : ''}`}>
<ProbeStatus probe={probe} />{' '}
{`${probe.displayName}${probe.countryCode ? `, ${probe.countryCode}` : ''} ${
probe.provider ? `(${probe.provider})` : ''
}`}
{!isCompatible && selectedChannel && (
<span className={styles.incompatibleText}> (incompatible with {selectedChannel})</span>
)}
{probe.deprecated && (
<DeprecationNotice
tooltipContent={
<div>
This probe is deprecated and will be removed soon. For more information{' '}
<TextLink
variant={'bodySmall'}
href="https://grafana.com/docs/grafana-cloud/whats-new/2025-01-14-launch-and-shutdown-dates-for-synthetics-probes-in-february-2025/"
external
>
click here.
</TextLink>
</div>
}
/>
)}
</div>
</Label>
{k6Version && (
<div className={styles.k6Version}>
(k6: {k6Version})
</div>
)}
</div>
</Label>
</div>
))}
</div>
);
})}
</div>
</div>
);
Expand Down Expand Up @@ -169,4 +235,31 @@ const getStyles = (theme: GrafanaTheme2) => ({
lineHeight: theme.typography.body.lineHeight,
marginBottom: '0',
}),

probeContent: css({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
flex: 1,
gap: theme.spacing(1),
}),

k6Version: css({
color: theme.colors.text.secondary,
fontSize: theme.typography.bodySmall.fontSize,
}),

incompatibleItem: css({
opacity: 0.5,
}),

incompatibleLabel: css({
color: theme.colors.text.disabled,
}),

incompatibleText: css({
color: theme.colors.warning.text,
fontSize: theme.typography.bodySmall.fontSize,
fontWeight: theme.typography.fontWeightMedium,
}),
});
15 changes: 14 additions & 1 deletion src/components/ProbeCard/ProbeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { GrafanaTheme2 } from '@grafana/data';
import { Card, Link, LinkButton, Stack, TextLink, useStyles2 } from '@grafana/ui';
import { css } from '@emotion/css';

import { type ExtendedProbe, type Label } from 'types';
import { type ExtendedProbe, FeatureName,type Label } from 'types';
import { AppRoutes } from 'routing/types';
import { generateRoutePath } from 'routing/utils';
import { useCanEditProbe } from 'hooks/useCanEditProbe';
import { PROBE_REACHABILITY_DESCRIPTION } from 'components/constants';
import { DeprecationNotice } from 'components/DeprecationNotice/DeprecationNotice';
import { FeatureFlag } from 'components/FeatureFlag';
import { SuccessRateGaugeProbe } from 'components/Gauges';

import { ProbeUsageLink } from '../ProbeUsageLink';
Expand Down Expand Up @@ -54,6 +55,18 @@ export const ProbeCard = ({ probe }: { probe: ExtendedProbe }) => {

<Card.Meta>
<div>Version: {probe.version}</div>
<FeatureFlag name={FeatureName.VersionManagement}>
{({ isEnabled }) =>
isEnabled ? (
<>
{!probe.public && probe.k6Version && <div>k6 version: {probe.k6Version}</div>}
{probe.supportsBinaryProvisioning && (
<div>k6 version management: supported</div>
)}
</>
) : null
}
</FeatureFlag>
</Card.Meta>

<Card.Description className={styles.extendedDescription}>
Expand Down
15 changes: 14 additions & 1 deletion src/components/ProbeStatus/ProbeStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import {
} from '@grafana/ui';
import { css } from '@emotion/css';

import { type ExtendedProbe } from 'types';
import { type ExtendedProbe, FeatureName } from 'types';
import { formatDate } from 'utils';
import { useResetProbeToken } from 'data/useProbes';
import { useCanEditProbe } from 'hooks/useCanEditProbe';
import { PROBE_REACHABILITY_DESCRIPTION } from 'components/constants';
import { DeprecationNotice } from 'components/DeprecationNotice/DeprecationNotice';
import { FeatureFlag } from 'components/FeatureFlag';
import { SuccessRateGaugeProbe } from 'components/Gauges';

import { ProbeUsageLink } from '../ProbeUsageLink';
Expand Down Expand Up @@ -98,6 +99,18 @@ export const ProbeStatus = ({ probe, onReset, readOnly }: ProbeStatusProps) => {
<SuccessRateGaugeProbe probeName={probe.name} height={200} width={300} description={PROBE_REACHABILITY_DESCRIPTION} />
<div className={styles.metaWrapper}>
<Meta title="Version:" value={probe.version} />
<FeatureFlag name={FeatureName.VersionManagement}>
{({ isEnabled }) =>
isEnabled ? (
<>
{!probe.public && probe.k6Version && <Meta title="k6 version:" value={probe.k6Version} />}
{probe.supportsBinaryProvisioning && (
<Meta title="k6 version management:" value="supported" />
)}
</>
) : null
}
</FeatureFlag>
<Meta
title={`Last ${probe.online ? `offline` : `online`}:`}
value={neverOnline ? `Never` : formatDate(probe.onlineChange * 1000)}
Expand Down
3 changes: 3 additions & 0 deletions src/schemas/forms/ProbeSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export const probeSchema: ZodType<Probe> = z.object({
labels: labelsSchema,
version: z.string(),
deprecated: z.boolean(),
k6Version: z.string().optional(),
supportsBinaryProvisioning: z.boolean().optional(),
supportedChannels: z.array(z.string()).optional(),
capabilities: z.object({
disableScriptedChecks: z.boolean(),
disableBrowserChecks: z.boolean(),
Expand Down
47 changes: 28 additions & 19 deletions src/test/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,34 @@ const baseCheckModel = ({ sequence }: { sequence: number }) => ({
created: Math.floor(faker.date.past().getTime() / 1000),
});

const baseProbeModel = ({ sequence }: { sequence: number }) => ({
id: sequence,
name: `${faker.lorem.word()}_${sequence}`,
public: faker.datatype.boolean(),
latitude: faker.location.latitude(),
longitude: faker.location.longitude(),
region: faker.helpers.arrayElement(['EMEA', 'AMER', 'APAC']),
labels: [{ name: faker.animal.petName(), value: faker.color.human() }],
online: true,
onlineChange: Math.floor(faker.date.past().getTime() / 1000),
version: faker.system.semver(),
deprecated: false,
modified: Math.floor(faker.date.recent().getTime() / 1000),
created: Math.floor(faker.date.past().getTime() / 1000),
capabilities: {
disableScriptedChecks: false,
disableBrowserChecks: false,
},
});
const baseProbeModel = ({ sequence }: { sequence: number }) => {
const supportsBinaryProvisioning = faker.datatype.boolean();

return {
id: sequence,
name: `${faker.lorem.word()}_${sequence}`,
public: faker.datatype.boolean(),
latitude: faker.location.latitude(),
longitude: faker.location.longitude(),
region: faker.helpers.arrayElement(['EMEA', 'AMER', 'APAC']),
labels: [{ name: faker.animal.petName(), value: faker.color.human() }],
online: true,
onlineChange: Math.floor(faker.date.past().getTime() / 1000),
version: faker.system.semver(),
deprecated: false,
modified: Math.floor(faker.date.recent().getTime() / 1000),
created: Math.floor(faker.date.past().getTime() / 1000),
k6Version: supportsBinaryProvisioning ? undefined : faker.system.semver(), // Legacy probes have static k6 version
supportsBinaryProvisioning,
supportedChannels: supportsBinaryProvisioning
? faker.helpers.arrayElements(['v1', 'v2', 'fast'], { min: 1, max: 3 })
: faker.helpers.arrayElements(['v1'], { min: 1, max: 1 }), // Legacy probes support limited channels
capabilities: {
disableScriptedChecks: false,
disableBrowserChecks: false,
},
};
};

const tlsConfig = () => ({
caCert: faker.helpers.maybe(() => faker.string.uuid()),
Expand Down
21 changes: 19 additions & 2 deletions src/test/fixtures/probes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,44 @@ export const PRIVATE_PROBE: Probe = db.probe.build({
{ name: 'Mr', value: 'Orange' },
{ name: 'chimi', value: 'churri' },
],
k6Version: 'v0.54.1',
supportsBinaryProvisioning: false,
supportedChannels: ['v1'],
});

export const PUBLIC_PROBE: Probe = db.probe.build({
public: true,
online: false,
supportsBinaryProvisioning: true,
supportedChannels: ['v1', 'v2', 'fast'],
});

export const ONLINE_PROBE: Probe = db.probe.build({});
export const ONLINE_PROBE: Probe = db.probe.build({
supportsBinaryProvisioning: true,
supportedChannels: ['v1', 'v2'],
});

export const OFFLINE_PROBE: Probe = db.probe.build({
online: false,
k6Version: 'v1.2.3',
supportsBinaryProvisioning: false,
supportedChannels: ['v1'],
});

export const SCRIPTED_DISABLED_PROBE: Probe = db.probe.build({
capabilities: {
disableScriptedChecks: true,
disableBrowserChecks: true,
},
supportsBinaryProvisioning: true,
supportedChannels: ['v2'],
});

export const UNSELECTED_PRIVATE_PROBE: Probe = db.probe.build({});
export const UNSELECTED_PRIVATE_PROBE: Probe = db.probe.build({
k6Version: 'v2.3.4',
supportsBinaryProvisioning: false,
supportedChannels: ['v2'],
});

export const DEFAULT_PROBES = [PRIVATE_PROBE, PUBLIC_PROBE, UNSELECTED_PRIVATE_PROBE];

Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export interface Probe extends ExistingObject {
deprecated: boolean;
k6Version?: string; // For legacy probes: static version like "v0.54.1". For modern probes: may be empty
supportsBinaryProvisioning?: boolean; // whether probe supports version management
supportedChannels?: string[]; // List of channel IDs that this probe supports
capabilities: ProbeCapabilities;
}

Expand Down