Skip to content
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
Expand Up @@ -345,6 +345,7 @@ export const EncryptedSyntheticsMonitorCodec = t.union([
export const SyntheticsMonitorWithIdCodec = t.intersection([
SyntheticsMonitorCodec,
t.interface({ id: t.string, updated_at: t.string, created_at: t.string }),
t.partial({ spaces: t.array(t.string), spaceId: t.string, revision: t.number }),
]);

const HeartbeatFieldsCodec = t.intersection([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@ export interface AgentPolicyInfo {
description?: string;
namespace?: string;
}

export interface PackagePolicyLink {
locationId: string;
locationLabel: string;
agentPolicyId: string;
packagePolicyId: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,52 @@ import { useFetcher } from '@kbn/observability-shared-plugin/public';
import { i18n } from '@kbn/i18n';

import {
EuiBasicTable,
EuiButtonEmpty,
EuiCallOut,
EuiFlyout,
EuiButton,
EuiCodeBlock,
EuiFlyoutHeader,
EuiTitle,
EuiFlyoutFooter,
EuiHorizontalRule,
EuiLink,
EuiSpacer,
EuiFlyoutBody,
EuiToolTip,
EuiSwitch,
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import type { EuiBasicTableColumn } from '@elastic/eui';

import yaml from 'js-yaml';
import { useSyntheticsSettingsContext } from '../../../contexts';
import { LoadingState } from '../../monitors_page/overview/overview/monitor_detail_flyout';
import type { SyntheticsMonitor } from '../../../../../../common/runtime_types';
import type {
SyntheticsMonitor,
SyntheticsMonitorWithId,
} from '../../../../../../common/runtime_types';
import { MonitorTypeEnum } from '../../../../../../common/runtime_types';
import type { MonitorInspectResponse } from '../../../state/monitor_management/api';
import { inspectMonitorAPI } from '../../../state/monitor_management/api';
import type {
MonitorInspectResponse,
PackagePolicyLink,
} from '../../../state/monitor_management/api';
import {
fetchMonitorAPI,
inspectMonitorAPI,
updateMonitorAPI,
} from '../../../state/monitor_management/api';
import { kibanaService } from '../../../../../utils/kibana_service';

interface InspectorProps {
isValid: boolean;
monitorFields: SyntheticsMonitor;
isEditFlow?: boolean;
}

export const MonitorInspect = ({ isValid, monitorFields }: InspectorProps) => {
export const MonitorInspect = ({ isValid, monitorFields, isEditFlow = false }: InspectorProps) => {
const { isDev } = useSyntheticsSettingsContext();

const [hideParams, setHideParams] = useState(() => !isDev);
Expand All @@ -51,6 +68,7 @@ export const MonitorInspect = ({ isValid, monitorFields }: InspectorProps) => {
};

const [isInspecting, setIsInspecting] = useState(false);
const [migrateCount, setMigrateCount] = useState(0);
const onButtonClick = () => {
setIsInspecting(() => !isInspecting);
setIsFlyoutVisible(() => !isFlyoutVisible);
Expand All @@ -66,7 +84,7 @@ export const MonitorInspect = ({ isValid, monitorFields }: InspectorProps) => {
// FIXME: Dario couldn't find a solution for monitorFields
// which is not memoized downstream
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isInspecting, hideParams]);
}, [isInspecting, hideParams, migrateCount]);

let flyout;

Expand Down Expand Up @@ -111,6 +129,17 @@ export const MonitorInspect = ({ isValid, monitorFields }: InspectorProps) => {
{formatContent(data.result, asJson)}
</EuiCodeBlock>
{data.decodedCode && <MonitorCode code={data.decodedCode} />}
{isEditFlow && (data.packagePolicyLinks.length > 0 || data.hasMissingReferences) && (
<>
<EuiHorizontalRule />
<PackagePolicyLinksTable
links={data.packagePolicyLinks}
hasMissingReferences={data.hasMissingReferences}
monitorFields={monitorFields}
onMigrateSuccess={() => setMigrateCount((c) => c + 1)}
/>
</>
)}
</>
) : loading && !error ? (
<LoadingState />
Expand Down Expand Up @@ -149,6 +178,124 @@ export const MonitorInspect = ({ isValid, monitorFields }: InspectorProps) => {
);
};

const stripServerFields = ({
created_at: _ca,
updated_at: _ua,
spaceId: _si,
revision: _rev,
...savedMonitor
}: SyntheticsMonitorWithId): SyntheticsMonitor => savedMonitor;

const PackagePolicyLinksTable = ({
links,
hasMissingReferences,
monitorFields,
onMigrateSuccess,
}: {
links: PackagePolicyLink[];
hasMissingReferences: boolean;
monitorFields: SyntheticsMonitor;
onMigrateSuccess: () => void;
}) => {
const { basePath } = useSyntheticsSettingsContext();
const [isMigrating, setIsMigrating] = useState(false);

const handleMigrate = async () => {
const monitorId = monitorFields.config_id;
if (!monitorId) return;
setIsMigrating(true);
try {
const savedMonitor = stripServerFields(await fetchMonitorAPI({ id: monitorId }));
await updateMonitorAPI({ monitor: savedMonitor, id: monitorId });
kibanaService.toasts.addSuccess({
title: MIGRATE_SUCCESS_LABEL,
toastLifeTimeMs: 3000,
});
onMigrateSuccess();
} catch (err) {
kibanaService.toasts.addError(err, { title: MIGRATE_FAILURE_LABEL });
} finally {
setIsMigrating(false);
}
};

const columns: Array<EuiBasicTableColumn<PackagePolicyLink>> = [
{
field: 'locationLabel',
name: PRIVATE_LOCATION_LABEL,
},
{
field: 'agentPolicyId',
name: AGENT_POLICY_LABEL,
render: (agentPolicyId: string) => (
<EuiLink
data-test-subj="syntheticsPackagePolicyAgentPolicyLink"
href={`${basePath}/app/fleet/policies/${agentPolicyId}`}
target="_blank"
external
>
{agentPolicyId}
</EuiLink>
),
},
{
field: 'packagePolicyId',
name: PACKAGE_POLICY_LABEL,
render: (packagePolicyId: string, item: PackagePolicyLink) => (
<EuiLink
data-test-subj="syntheticsPackagePolicyLink"
href={`${basePath}/app/fleet/policies/${item.agentPolicyId}/edit-integration/${packagePolicyId}`}
target="_blank"
external
>
{packagePolicyId}
</EuiLink>
),
},
];

return (
<>
<EuiTitle size="s">
<h3>{LINKED_POLICIES_LABEL}</h3>
</EuiTitle>
<EuiSpacer size="s" />
{hasMissingReferences && (
<>
<EuiCallOut
title={MISSING_REFERENCES_TITLE}
color="warning"
iconType="warning"
size="s"
announceOnMount
data-test-subj="syntheticsPackagePolicyMissingReferencesCallout"
>
<p>{MISSING_REFERENCES_DESCRIPTION}</p>
<EuiButtonEmpty
size="s"
color="warning"
isLoading={isMigrating}
onClick={handleMigrate}
data-test-subj="syntheticsPackagePolicyMigrateButton"
>
{MIGRATE_LABEL}
</EuiButtonEmpty>
</EuiCallOut>
<EuiSpacer size="s" />
</>
)}
{links.length > 0 && (
<EuiBasicTable
tableCaption={LINKED_POLICIES_LABEL}
items={links}
columns={columns}
data-test-subj="syntheticsPackagePolicyLinksTable"
/>
)}
</>
);
};

// @ts-ignore: Unused variable
// tslint:disable-next-line: no-unused-variable
const MonitorCode = ({ code }: { code: string }) => (
Expand Down Expand Up @@ -219,3 +366,54 @@ const HIDE_PARAMS = i18n.translate('xpack.synthetics.monitorInspect.hideParams',
const AS_JSON = i18n.translate('xpack.synthetics.monitorInspect.asJson', {
defaultMessage: 'As JSON',
});

const LINKED_POLICIES_LABEL = i18n.translate(
'xpack.synthetics.monitorInspect.linkedPoliciesLabel',
{
defaultMessage: 'Linked Fleet policies',
}
);

const PRIVATE_LOCATION_LABEL = i18n.translate(
'xpack.synthetics.monitorInspect.privateLocationLabel',
{
defaultMessage: 'Private location',
}
);

const AGENT_POLICY_LABEL = i18n.translate('xpack.synthetics.monitorInspect.agentPolicyLabel', {
defaultMessage: 'Agent policy',
});

const PACKAGE_POLICY_LABEL = i18n.translate('xpack.synthetics.monitorInspect.packagePolicyLabel', {
defaultMessage: 'Package policy',
});

const MISSING_REFERENCES_TITLE = i18n.translate(
'xpack.synthetics.monitorInspect.missingReferencesTitle',
{
defaultMessage: 'Package policy references not found',
}
);

const MISSING_REFERENCES_DESCRIPTION = i18n.translate(
'xpack.synthetics.monitorInspect.missingReferencesDescription',
{
defaultMessage:
'This monitor has package policies that use a legacy ID format. Click Migrate now to update them.',
}
);

const MIGRATE_LABEL = i18n.translate('xpack.synthetics.monitorInspect.migrateLabel', {
defaultMessage: 'Migrate now',
});

const MIGRATE_SUCCESS_LABEL = i18n.translate(
'xpack.synthetics.monitorInspect.migrateSuccessLabel',
{ defaultMessage: 'Monitor policies migrated successfully' }
);

const MIGRATE_FAILURE_LABEL = i18n.translate(
'xpack.synthetics.monitorInspect.migrateFailureLabel',
{ defaultMessage: 'Failed to migrate monitor policies' }
);
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ export const MonitorSteps = ({
)}
<AdvancedConfig readOnly={readOnly} />
<MonitorTypePortal monitorType={type} />
<InspectMonitorPortal isValid={formState.isValid} monitorFields={format(watch())} />
<InspectMonitorPortal
isValid={formState.isValid}
monitorFields={format(watch())}
isEditFlow={isEditFlow}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import { InspectMonitorPortalNode } from '../portals';
export const InspectMonitorPortal = ({
isValid,
monitorFields,
isEditFlow = false,
}: {
isValid: boolean;
monitorFields: SyntheticsMonitor;
isEditFlow?: boolean;
}) => {
return (
<InPortal node={InspectMonitorPortalNode}>
<MonitorInspect isValid={isValid} monitorFields={monitorFields} />
<MonitorInspect isValid={isValid} monitorFields={monitorFields} isEditFlow={isEditFlow} />
</InPortal>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import type {
SyntheticsMonitorWithId,
} from '../../../../../common/runtime_types';
import { INITIAL_REST_VERSION, SYNTHETICS_API_URLS } from '../../../../../common/constants';
import type { PackagePolicyLink } from '../../../../../common/types';
export type { PackagePolicyLink };

export type UpsertMonitorResponse = ServiceLocationErrorsResponse | SyntheticsMonitorWithId;

Expand All @@ -34,18 +36,31 @@ export interface MonitorInspectResponse {
privateConfig: PackagePolicy | null;
}

export interface InspectMonitorAPIResponse {
result: MonitorInspectResponse;
decodedCode: string;
packagePolicyLinks: PackagePolicyLink[];
hasMissingReferences: boolean;
}

export const inspectMonitorAPI = async ({
monitor,
hideParams,
}: {
hideParams?: boolean;
monitor: SyntheticsMonitor | EncryptedSyntheticsMonitor;
}): Promise<{ result: MonitorInspectResponse; decodedCode: string }> => {
}): Promise<InspectMonitorAPIResponse> => {
return await apiService.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITOR_INSPECT, monitor, undefined, {
hideParams,
});
};

export const fetchMonitorAPI = async ({ id }: { id: string }): Promise<SyntheticsMonitorWithId> => {
return await apiService.get<SyntheticsMonitorWithId>(
SYNTHETICS_API_URLS.GET_SYNTHETICS_MONITOR.replace('{monitorId}', id)
);
};

export const updateMonitorAPI = async ({
monitor,
id,
Expand Down
Loading
Loading