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 @@ -70,7 +70,6 @@ export {
type ReviewApproveMenuClickedPayload,
type ApproveModalCancelClickedPayload,
type ApproveModalApproveClickedPayload,
type IntegrationDeleteConfirmedPayload,
type DataStreamDeleteConfirmedPayload,
type DataStreamRefreshConfirmedPayload,
type PipelineEditedPayload,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,42 +32,41 @@ export enum AIV2TelemetryEventType {
// Fleet Events
ManageIntegrationsTableViewed = 'aiv2_manage_integrations_table_viewed',
UploadIntegrationClicked = 'aiv2_upload_integration_clicked',
IntegrationDeleteConfirmed = 'aiv2_integration_delete_confirmed',
ReviewApproveMenuClicked = 'aiv2_review_approve_menu_clicked',
IntegrationDownloadZipClicked = 'aiv2_integration_download_zip_clicked',
ApproveModalCancelClicked = 'aiv2_approve_modal_cancel_clicked',
ApproveModalApproveClicked = 'aiv2_approve_modal_approve_clicked',
}

export interface CreateIntegrationPageLoadedPayload {
sessionId: string;
sessionId?: string;
}

export interface DataStreamFlyoutOpenedPayload {
sessionId: string;
sessionId?: string;
/** Boolean flag if this is the first data stream being created for a new integration */
isFirstDataStream: boolean;
}

export interface EditDataStreamFlyoutOpenedPayload {
sessionId: string;
sessionId?: string;
}

export interface AnalyzeLogsTriggeredPayload {
sessionId: string;
sessionId?: string;
logsSource: LogsSource;
}

export interface EditPipelineTabOpenedPayload {
sessionId: string;
sessionId?: string;
}

export interface CodeEditorCopyClickedPayload {
sessionId: string;
sessionId?: string;
}

export interface DataStreamCreationCompletePayload {
sessionId: string;
sessionId?: string;
integrationId: string;
integrationName: string;
dataStreamId: string;
Expand All @@ -78,7 +77,7 @@ export interface DataStreamCreationCompletePayload {
}

export interface IntegrationInstalledPayload {
sessionId: string;
sessionId?: string;
integrationName: string;
version: string;
dataStreamCount: number;
Expand All @@ -89,31 +88,28 @@ export type ManageIntegrationsTableViewedPayload = Record<string, never>;
export type UploadIntegrationClickedPayload = Record<string, never>;

export interface CancelButtonClickedPayload {
sessionId: string;
sessionId?: string;
}

export interface DoneButtonClickedPayload {
sessionId: string;
sessionId?: string;
}

export type ReviewApproveMenuClickedPayload = Record<string, never>;
export type IntegrationDownloadZipClickedPayload = Record<string, never>;
export type ApproveModalCancelClickedPayload = Record<string, never>;
export type ApproveModalApproveClickedPayload = Record<string, never>;
export interface IntegrationDeleteConfirmedPayload {
sessionId: string;
}

export interface DataStreamDeleteConfirmedPayload {
sessionId: string;
sessionId?: string;
}

export interface DataStreamRefreshConfirmedPayload {
sessionId: string;
sessionId?: string;
}

export interface PipelineEditedPayload {
sessionId: string;
sessionId?: string;
linesAdded: number;
linesRemoved: number;
netLineChange: number;
Expand Down Expand Up @@ -152,8 +148,6 @@ export type AIV2EventPayload<T extends AIV2TelemetryEventType> =
? ApproveModalCancelClickedPayload
: T extends AIV2TelemetryEventType.ApproveModalApproveClicked
? ApproveModalApproveClickedPayload
: T extends AIV2TelemetryEventType.IntegrationDeleteConfirmed
? IntegrationDeleteConfirmedPayload
: T extends AIV2TelemetryEventType.DataStreamDeleteConfirmed
? DataStreamDeleteConfirmedPayload
: T extends AIV2TelemetryEventType.DataStreamRefreshConfirmed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const mockNavigateToUrl = jest.fn();
const mockGetUrlForApp = jest.fn(() => '/mock-integrations-url');
const mockReportCancelButtonClicked = jest.fn();
const mockReportDoneButtonClicked = jest.fn();
const mockReportIntegrationDeleteConfirmed = jest.fn();
const mockUseGetIntegrationById = jest.fn();
const mockDeleteIntegrationMutateAsync = jest.fn().mockResolvedValue(undefined);

Expand Down Expand Up @@ -77,7 +76,6 @@ jest.mock('../telemetry_context', () => ({
useTelemetry: () => ({
reportCancelButtonClicked: mockReportCancelButtonClicked,
reportDoneButtonClicked: mockReportDoneButtonClicked,
reportIntegrationDeleteConfirmed: mockReportIntegrationDeleteConfirmed,
}),
}));

Expand Down Expand Up @@ -271,7 +269,6 @@ describe('IntegrationManagement telemetry', () => {
});
});

expect(mockReportIntegrationDeleteConfirmed).toHaveBeenCalledTimes(1);
expect(mockNavigateToApp).toHaveBeenCalledWith('integrations', expect.any(Object));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ const IntegrationManagementContents: React.FC = () => {
);
const [isDeleteIntegrationModalVisible, setIsDeleteIntegrationModalVisible] = useState(false);
const deleteIntegrationModalTitleId = useGeneratedHtmlId();
const { reportCancelButtonClicked, reportDoneButtonClicked, reportIntegrationDeleteConfirmed } =
useTelemetry();
const { reportCancelButtonClicked, reportDoneButtonClicked } = useTelemetry();

const navigateToManage = useCallback(() => {
application.navigateToApp(INTEGRATIONS_APP_ID, { path: INTEGRATIONS_MANAGE_PATH });
Expand Down Expand Up @@ -77,17 +76,11 @@ const IntegrationManagementContents: React.FC = () => {
try {
await deleteIntegrationMutation.mutateAsync({ integrationId });
setIsDeleteIntegrationModalVisible(false);
reportIntegrationDeleteConfirmed();
navigateToManage();
} catch {
// Error toast is shown by useDeleteIntegration
}
}, [
deleteIntegrationMutation,
integrationId,
navigateToManage,
reportIntegrationDeleteConfirmed,
]);
}, [deleteIntegrationMutation, integrationId, navigateToManage]);

const handleDone = useCallback(() => {
reportDoneButtonClicked();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,29 @@ describe('CreateDataStreamFlyout', () => {

expect(getByTestId('analyzeLogsButton')).toBeDisabled();
});

it('should keep analyze button disabled when data stream description is empty', async () => {
const Wrapper = createWrapper({
title: 'Integration',
description: 'Integration description',
connectorId: 'connector-1',
dataStreamTitle: 'My stream',
dataStreamDescription: '',
dataCollectionMethod: ['filestream'],
logSample: '2024-01-01 level=info msg=test',
});
const { getByTestId } = render(
<Wrapper>
<CreateDataStreamFlyout onClose={mockOnClose} />
</Wrapper>
);

await waitFor(() => {
expect(getByTestId('analyzeLogsButton')).toBeInTheDocument();
});

expect(getByTestId('analyzeLogsButton')).toBeDisabled();
});
});

describe('log source selection', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
EuiCallOut,
EuiCheckableCard,
EuiFilePicker,
EuiToolTip,
useEuiTheme,
} from '@elastic/eui';
import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
Expand All @@ -39,6 +40,7 @@ import {
import { PLUGIN_ID } from '../../../../../common/constants';
import type { LogsSourceOption } from '../../forms/types';
import { useIntegrationForm } from '../../forms/integration_form';
import * as formI18n from '../../forms/translations';
import * as i18n from './translations';
import { FormStyledLabel } from '../../../../common/components/form_styled_label';
import {
Expand Down Expand Up @@ -132,6 +134,7 @@ const dataCollectionMethodOptions: Array<EuiComboBoxOptionOption<string>> = [
];

export const CreateDataStreamFlyout: React.FC<CreateDataStreamFlyoutProps> = ({ onClose }) => {
const { euiTheme } = useEuiTheme();
const styles = useLayoutStyles();
const { integrationId: currentIntegrationId } = useParams<{ integrationId?: string }>();
const { reportAnalyzeLogsTriggered } = useTelemetry();
Expand Down Expand Up @@ -225,7 +228,6 @@ export const CreateDataStreamFlyout: React.FC<CreateDataStreamFlyoutProps> = ({
[validateIndex, clearIndexValidationError]
);

// Check if integration-level required fields are filled (only when creating new integration)
const isIntegrationFieldsValid =
!!formData?.title?.trim() && !!formData?.description?.trim() && !!formData?.connectorId?.trim();

Expand All @@ -240,6 +242,7 @@ export const CreateDataStreamFlyout: React.FC<CreateDataStreamFlyoutProps> = ({

const isDataStreamFieldsValid =
!!formData?.dataStreamTitle?.trim() &&
!!formData?.dataStreamDescription?.trim() &&
!hasDuplicateDataStreamName &&
formData?.dataCollectionMethod != null &&
formData.dataCollectionMethod.length > 0;
Expand All @@ -257,6 +260,87 @@ export const CreateDataStreamFlyout: React.FC<CreateDataStreamFlyoutProps> = ({
isLoading ||
isUploadingSamples;

const analyzeLogsValidationReasons = useMemo(() => {
const reasons: string[] = [];
if (!formData?.title?.trim()) {
reasons.push(formI18n.TITLE_REQUIRED);
}
if (!formData?.description?.trim()) {
reasons.push(formI18n.DESCRIPTION_REQUIRED);
}
if (!formData?.connectorId?.trim()) {
reasons.push(formI18n.CONNECTOR_REQUIRED);
}
if (!formData?.dataStreamTitle?.trim()) {
reasons.push(formI18n.DATA_STREAM_TITLE_REQUIRED);
} else if (hasDuplicateDataStreamName) {
reasons.push(formI18n.DATA_STREAM_TITLE_ALREADY_EXISTS);
}
if (!formData?.dataStreamDescription?.trim()) {
reasons.push(formI18n.DATA_STREAM_DESCRIPTION_REQUIRED);
}
if (!formData?.dataCollectionMethod?.length) {
reasons.push(formI18n.DATA_COLLECTION_METHOD_REQUIRED);
}
if (logsSourceOption === 'file' && !logSample) {
reasons.push(formI18n.LOG_SAMPLE_REQUIRED);
}
if (logsSourceOption === 'index') {
if (!selectedIndex) {
reasons.push(formI18n.SELECTED_INDEX_REQUIRED);
} else if (indexValidationError) {
reasons.push(indexValidationError);
}
}
return reasons;
}, [
formData?.title,
formData?.description,
formData?.connectorId,
formData?.dataStreamTitle,
formData?.dataStreamDescription,
formData?.dataCollectionMethod,
hasDuplicateDataStreamName,
logsSourceOption,
logSample,
selectedIndex,
indexValidationError,
]);

const analyzeLogsDisabledTooltipContent = useMemo(() => {
if (!isAnalyzeDisabled) {
return null;
}
if (analyzeLogsValidationReasons.length > 0) {
return (
<EuiText size="xs" component="div">
<ul
css={css`
margin-block: 0;
padding-inline-start: ${euiTheme.size.base};
`}
>
{analyzeLogsValidationReasons.map((reason, index) => (
<li key={`${reason}-${index}`}>{reason}</li>
))}
</ul>
</EuiText>
);
}
if (isParsing || isValidatingIndex || isLoading || isUploadingSamples) {
return i18n.ANALYZE_LOGS_DISABLED_LOADING;
}
return null;
}, [
analyzeLogsValidationReasons,
euiTheme.size.base,
isAnalyzeDisabled,
isLoading,
isParsing,
isUploadingSamples,
isValidatingIndex,
]);

const handleAnalyzeLogs = useCallback(async () => {
if (!formData) return;

Expand Down Expand Up @@ -447,6 +531,8 @@ export const CreateDataStreamFlyout: React.FC<CreateDataStreamFlyoutProps> = ({
<EuiText size="s" color="subdued">
{i18n.LOGS_SECTION_DESCRIPTION}
</EuiText>
<EuiSpacer size="xs" />
<EuiText size="s">{i18n.LOG_SAMPLE_REQUIRED_FOR_ANALYSIS}</EuiText>

<EuiSpacer size="m" />

Expand Down Expand Up @@ -530,15 +616,31 @@ export const CreateDataStreamFlyout: React.FC<CreateDataStreamFlyoutProps> = ({
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
fill
onClick={handleAnalyzeLogs}
disabled={isAnalyzeDisabled}
isLoading={isParsing || isLoading || isUploadingSamples}
data-test-subj="analyzeLogsButton"
>
{i18n.ANALYZE_LOGS_BUTTON}
</EuiButton>
{isAnalyzeDisabled && analyzeLogsDisabledTooltipContent != null ? (
<EuiToolTip content={analyzeLogsDisabledTooltipContent} position="top">
<span tabIndex={0}>
<EuiButton
fill
onClick={handleAnalyzeLogs}
disabled={isAnalyzeDisabled}
isLoading={isParsing || isLoading || isUploadingSamples}
data-test-subj="analyzeLogsButton"
>
{i18n.ANALYZE_LOGS_BUTTON}
</EuiButton>
</span>
</EuiToolTip>
) : (
<EuiButton
fill
onClick={handleAnalyzeLogs}
disabled={isAnalyzeDisabled}
isLoading={isParsing || isLoading || isUploadingSamples}
data-test-subj="analyzeLogsButton"
>
{i18n.ANALYZE_LOGS_BUTTON}
</EuiButton>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
Expand Down
Loading
Loading