Skip to content

Commit

Permalink
Add index subset selection to setup process
Browse files Browse the repository at this point in the history
  • Loading branch information
weltenwort committed Oct 16, 2019
1 parent b8647ae commit f3aa009
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,8 @@ export const isSetupStatusWithResults = (setupStatus: SetupStatus) =>
['skipped', 'hiddenAfterSuccess', 'skippedButReconfigurable', 'skippedButUpdatable'].includes(
setupStatus
);

const KIBANA_SAMPLE_DATA_INDICES = ['kibana_sample_data_logs*'];

export const isExampleDataIndex = (indexName: string) =>
KIBANA_SAMPLE_DATA_INDICES.includes(indexName);
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { useLogAnalysisCleanup } from './log_analysis_cleanup';
import { useStatusState } from './log_analysis_status_state';

const MODULE_ID = 'logs_ui_analysis';
const SAMPLE_DATA_INDEX = 'kibana_sample_data_logs*';

export const useLogAnalysisJobs = ({
indexPattern,
Expand All @@ -29,11 +28,10 @@ export const useLogAnalysisJobs = ({
spaceId: string;
timeField: string;
}) => {
const filteredIndexPattern = useMemo(() => removeSampleDataIndex(indexPattern), [indexPattern]);
const { cleanupMLResources } = useLogAnalysisCleanup({ sourceId, spaceId });
const [statusState, dispatch] = useStatusState({
bucketSpan,
indexPattern: filteredIndexPattern,
indexPattern,
timestampField: timeField,
});

Expand Down Expand Up @@ -62,15 +60,19 @@ export const useLogAnalysisJobs = ({
const [setupMlModuleRequest, setupMlModule] = useTrackedPromise(
{
cancelPreviousOn: 'resolution',
createPromise: async (start, end) => {
createPromise: async (
indices: string[],
start: number | undefined,
end: number | undefined
) => {
dispatch({ type: 'startedSetup' });
return await callSetupMlModuleAPI(
MODULE_ID,
start,
end,
spaceId,
sourceId,
filteredIndexPattern,
indices.join(','),
timeField,
bucketSpan
);
Expand All @@ -82,7 +84,7 @@ export const useLogAnalysisJobs = ({
dispatch({ type: 'failedSetup' });
},
},
[filteredIndexPattern, spaceId, sourceId, timeField, bucketSpan]
[spaceId, sourceId, timeField, bucketSpan]
);

const [fetchJobStatusRequest, fetchJobStatus] = useTrackedPromise(
Expand All @@ -99,7 +101,7 @@ export const useLogAnalysisJobs = ({
dispatch({ type: 'failedFetchingJobStatuses' });
},
},
[filteredIndexPattern, spaceId, sourceId]
[spaceId, sourceId]
);

const isLoadingSetupStatus = useMemo(
Expand All @@ -108,16 +110,18 @@ export const useLogAnalysisJobs = ({
[fetchJobStatusRequest.state, fetchModuleDefinitionRequest.state]
);

const availableIndices = useMemo(() => indexPattern.split(','), [indexPattern]);

const viewResults = useCallback(() => {
dispatch({ type: 'viewedResults' });
}, []);

const cleanupAndSetup = useCallback(
(start, end) => {
(indices: string[], start: number | undefined, end: number | undefined) => {
dispatch({ type: 'startedSetup' });
cleanupMLResources()
.then(() => {
setupMlModule(start, end);
setupMlModule(indices, start, end);
})
.catch(() => {
dispatch({ type: 'failedSetup' });
Expand All @@ -139,6 +143,7 @@ export const useLogAnalysisJobs = ({
}, [fetchModuleDefinition]);

return {
availableIndices,
fetchJobStatus,
isLoadingSetupStatus,
jobStatus: statusState.jobStatus,
Expand All @@ -153,11 +158,3 @@ export const useLogAnalysisJobs = ({
};

export const LogAnalysisJobs = createContainer(useLogAnalysisJobs);
//
// This is needed due to: https://github.com/elastic/kibana/issues/43671
const removeSampleDataIndex = (indexPattern: string) => {
return indexPattern
.split(',')
.filter(index => index !== SAMPLE_DATA_INDEX)
.join(',');
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,82 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { useState, useCallback } from 'react';

type SetupHandler = (startTime?: number | undefined, endTime?: number | undefined) => void;
import { useState, useCallback, useMemo } from 'react';

interface Props {
import { isExampleDataIndex } from '../../../../common/log_analysis';

type SetupHandler = (
indices: string[],
startTime: number | undefined,
endTime: number | undefined
) => void;

interface AnalysisSetupStateArguments {
availableIndices: string[];
cleanupAndSetupModule: SetupHandler;
setupModule: SetupHandler;
}

type IndicesSelection = Record<string, boolean>;

type ValidationErrors = 'TOO_FEW_SELECTED_INDICES';

const fourWeeksInMs = 86400000 * 7 * 4;

export const useAnalysisSetupState = ({ setupModule, cleanupAndSetupModule }: Props) => {
export const useAnalysisSetupState = ({
availableIndices,
cleanupAndSetupModule,
setupModule,
}: AnalysisSetupStateArguments) => {
const [startTime, setStartTime] = useState<number | undefined>(Date.now() - fourWeeksInMs);
const [endTime, setEndTime] = useState<number | undefined>(undefined);

const [selectedIndices, setSelectedIndices] = useState<IndicesSelection>(
availableIndices.reduce(
(indexMap, indexName) => ({
...indexMap,
[indexName]: !(availableIndices.length > 1 && isExampleDataIndex(indexName)),
}),
{}
)
);

const selectedIndexNames = useMemo(
() =>
Object.entries(selectedIndices)
.filter(([_indexName, isSelected]) => isSelected)
.map(([indexName]) => indexName),
[selectedIndices]
);

const setup = useCallback(() => {
return setupModule(startTime, endTime);
}, [setupModule, startTime, endTime]);
return setupModule(selectedIndexNames, startTime, endTime);
}, [setupModule, selectedIndexNames, startTime, endTime]);

const cleanupAndSetup = useCallback(() => {
return cleanupAndSetupModule(startTime, endTime);
}, [cleanupAndSetupModule, startTime, endTime]);
return cleanupAndSetupModule(selectedIndexNames, startTime, endTime);
}, [cleanupAndSetupModule, selectedIndexNames, startTime, endTime]);

const validationErrors: ValidationErrors[] = useMemo(
() => [
...(Object.values(selectedIndices).some(isSelected => isSelected)
? []
: ['TOO_FEW_SELECTED_INDICES' as const]),
],
[selectedIndices]
);

return {
cleanupAndSetup,
endTime,
selectedIndexNames,
selectedIndices,
setEndTime,
setSelectedIndices,
setStartTime,
setup,
startTime,
validationErrors,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,19 @@ const isJobConfigurationConsistent = (
return (
jobConfiguration &&
jobConfiguration.bucketSpan === sourceConfiguration.bucketSpan &&
jobConfiguration.indexPattern === sourceConfiguration.indexPattern &&
jobConfiguration.indexPattern &&
isIndexPatternSubset(jobConfiguration.indexPattern, sourceConfiguration.indexPattern) &&
jobConfiguration.timestampField === sourceConfiguration.timestampField
);
});

const isIndexPatternSubset = (indexPatternSubset: string, indexPatternSuperset: string) => {
const subsetSubPatterns = indexPatternSubset.split(',');
const supersetSubPatterns = new Set(indexPatternSuperset.split(','));

return subsetSubPatterns.every(subPattern => supersetSubPatterns.has(subPattern));
};

export const useStatusState = (sourceConfiguration: JobSourceConfiguration) => {
return useReducer(statusReducer, sourceConfiguration, createInitialState);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@ import { AnalysisUnavailableContent } from './page_unavailable_content';
import { AnalysisSetupStatusUnknownContent } from './page_setup_status_unknown';

export const AnalysisPageContent = () => {
const { sourceId, source } = useContext(Source.Context);
const { sourceId } = useContext(Source.Context);
const { hasLogAnalysisCapabilites } = useContext(LogAnalysisCapabilities.Context);

const { setup, cleanupAndSetup, setupStatus, viewResults, fetchJobStatus } = useContext(
LogAnalysisJobs.Context
);
const {
availableIndices,
cleanupAndSetup,
fetchJobStatus,
setup,
setupStatus,
viewResults,
} = useContext(LogAnalysisJobs.Context);

useEffect(() => {
fetchJobStatus();
Expand Down Expand Up @@ -50,10 +55,10 @@ export const AnalysisPageContent = () => {
} else {
return (
<AnalysisSetupContent
availableIndices={availableIndices}
setup={setup}
cleanupAndSetup={cleanupAndSetup}
setupStatus={setupStatus}
indexPattern={source ? source.configuration.logAlias : ''}
viewResults={viewResults}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,23 @@ import { SetupStatus } from '../../../../common/log_analysis';
import { useTrackPageview } from '../../../hooks/use_track_metric';
import { AnalysisSetupSteps } from './setup/steps';

type SetupHandler = (startTime?: number | undefined, endTime?: number | undefined) => void;
type SetupHandler = (
indices: string[],
startTime: number | undefined,
endTime: number | undefined
) => void;

interface AnalysisSetupContentProps {
availableIndices: string[];
cleanupAndSetup: SetupHandler;
indexPattern: string;
setup: SetupHandler;
setupStatus: SetupStatus;
viewResults: () => void;
}

export const AnalysisSetupContent: React.FunctionComponent<AnalysisSetupContentProps> = ({
availableIndices,
cleanupAndSetup,
indexPattern,
setup,
setupStatus,
viewResults,
Expand Down Expand Up @@ -71,10 +75,10 @@ export const AnalysisSetupContent: React.FunctionComponent<AnalysisSetupContentP
</EuiText>
<EuiSpacer />
<AnalysisSetupSteps
availableIndices={availableIndices}
setup={setup}
cleanupAndSetup={cleanupAndSetup}
viewResults={viewResults}
indexPattern={indexPattern}
setupStatus={setupStatus}
/>
</EuiPageContentBody>
Expand All @@ -86,7 +90,7 @@ export const AnalysisSetupContent: React.FunctionComponent<AnalysisSetupContentP

// !important due to https://github.com/elastic/eui/issues/2232
const AnalysisPageContent = euiStyled(EuiPageContent)`
max-width: 518px !important;
max-width: 768px !important;
`;

const AnalysisSetupPage = euiStyled(EuiPage)`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useMemo, useCallback } from 'react';

import { i18n } from '@kbn/i18n';
import { EuiCode, EuiDescribedFormGroup, EuiFormRow, EuiCheckboxGroup } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

const indicesSelectionLabel = i18n.translate('xpack.infra.analysisSetup.indicesSelectionLabel', {
defaultMessage: 'Indices',
});

type IndicesSelection = Record<string, boolean>;

export const AnalysisSetupIndicesForm: React.FunctionComponent<{
indices: IndicesSelection;
onChangeSelectedIndices: (selectedIndices: IndicesSelection) => void;
validationErrors?: Array<'TOO_FEW_SELECTED_INDICES'>;
}> = ({ indices, onChangeSelectedIndices, validationErrors = [] }) => {
const choices = useMemo(
() =>
Object.keys(indices).map(indexName => ({
id: indexName,
label: <EuiCode>{indexName}</EuiCode>,
})),
[indices]
);

const handleCheckboxGroupChange = useCallback(
indexName => {
onChangeSelectedIndices({
...indices,
[indexName]: !indices[indexName],
});
},
[indices, onChangeSelectedIndices]
);

return (
<EuiDescribedFormGroup
idAria="indices"
title={
<FormattedMessage
id="xpack.infra.analysisSetup.indicesSelectionTitle"
defaultMessage="Choose indices"
/>
}
description={
<FormattedMessage
id="xpack.infra.analysisSetup.indicesSelectionDescription"
defaultMessage="By default, Machine Learning analyzes log messages in all log indices configured for the source. You can choose to only analyse a subset of the index names. Every selected index name must match at least one index with log entries."
/>
}
>
<EuiFormRow
describedByIds={['indices']}
error={validationErrors.map(formatValidationError)}
fullWidth
isInvalid={validationErrors.length > 0}
label={indicesSelectionLabel}
labelType="legend"
>
<EuiCheckboxGroup
options={choices}
idToSelectedMap={indices}
onChange={handleCheckboxGroupChange}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
);
};

const formatValidationError = (validationError: 'TOO_FEW_SELECTED_INDICES') => {
switch (validationError) {
case 'TOO_FEW_SELECTED_INDICES':
return i18n.translate(
'xpack.infra.analysisSetup.indicesSelectionTooFewSelectedIndicesDescription',
{
defaultMessage: 'Select at least one index name.',
}
);
}
};
Loading

0 comments on commit f3aa009

Please sign in to comment.