Skip to content

Commit

Permalink
[AIOps] Explain Log Rate Spikes: create shareable component containin…
Browse files Browse the repository at this point in the history
…g only analysis (#158629)

## Summary

This PR exposes the `ExplainLogRateSpikesContent` shared component so
that it can be used independently of the search bar/datepicker
- The component accepts various external settings including a timerange
and query to run the analysis against.
- The `ExplainLogRateSpikesPage` component now uses the
`ExplainLogRateSpikesContent` component
- The `useData` hook has been simplified - the set up for the search
query has been extracted into a separate hook

<img width="1245" alt="image"
src="https://github.com/elastic/kibana/assets/6446462/30dec4b2-3162-4a39-b598-0dec70993fa7">

This is the first step for the Observability Alert Details Page
Integration.

Style edits:

The component now uses EUI's ResizeableContainer to allow the main
histogram to be sticky.
Also adds some style updates from
#156605


### Checklist

Delete any items that are not applicable to this PR.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
  • Loading branch information
alvarezmelissa87 committed May 30, 2023
1 parent 0ff50e1 commit 03d4fe7
Show file tree
Hide file tree
Showing 19 changed files with 601 additions and 295 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
EuiButton,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiIconTip,
EuiProgress,
EuiText,
Expand Down Expand Up @@ -45,47 +46,23 @@ export const ProgressControls: FC<ProgressControlProps> = ({
}) => {
const { euiTheme } = useEuiTheme();
const runningProgressBarStyles = useAnimatedProgressBarBackground(euiTheme.colors.success);
const analysisCompleteStyle = { display: 'none' };

return (
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem data-test-subj="aiopProgressTitle">
<EuiText size="xs" color="subdued">
<FormattedMessage
data-test-subj="aiopsProgressTitleMessage"
id="xpack.aiops.progressTitle"
defaultMessage="Progress: {progress}% — {progressMessage}"
values={{ progress: Math.round(progress * 100), progressMessage }}
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem css={isRunning ? runningProgressBarStyles : undefined}>
<EuiProgress
aria-label={i18n.translate('xpack.aiops.progressAriaLabel', {
defaultMessage: 'Progress',
})}
value={Math.round(progress * 100)}
max={100}
size="m"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{!isRunning && (
<EuiButton
data-test-subj={`aiopsRerunAnalysisButton${shouldRerunAnalysis ? ' shouldRerun' : ''}`}
size="s"
onClick={onRefresh}
color={shouldRerunAnalysis ? 'warning' : 'primary'}
fill
>
<EuiFlexGroup>
<EuiFlexItem>
<FormattedMessage
id="xpack.aiops.rerunAnalysisButtonTitle"
defaultMessage="Rerun analysis"
defaultMessage="Run analysis"
/>
</EuiFlexItem>
{shouldRerunAnalysis && (
Expand All @@ -107,11 +84,53 @@ export const ProgressControls: FC<ProgressControlProps> = ({
</EuiButton>
)}
{isRunning && (
<EuiButton data-test-subj="aiopsCancelAnalysisButton" size="s" onClick={onCancel} fill>
<EuiButton data-test-subj="aiopsCancelAnalysisButton" size="s" onClick={onCancel}>
<FormattedMessage id="xpack.aiops.cancelAnalysisButtonTitle" defaultMessage="Cancel" />
</EuiButton>
)}
</EuiFlexItem>
<EuiFlexItem>
{progress === 1 ? (
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
<EuiFlexItem grow={false}>
<EuiIcon type="checkInCircleFilled" color={euiTheme.colors.success} />
</EuiFlexItem>
<EuiFlexItem grow={false} data-test-subj="aiopsAnalysisComplete">
<small>
{i18n.translate('xpack.aiops.analysisCompleteLabel', {
defaultMessage: 'Analysis complete',
})}
</small>
</EuiFlexItem>
</EuiFlexGroup>
) : null}
<EuiFlexGroup
direction="column"
gutterSize="none"
css={progress === 1 ? analysisCompleteStyle : undefined}
>
<EuiFlexItem data-test-subj="aiopProgressTitle">
<EuiText size="xs" color="subdued">
<FormattedMessage
data-test-subj="aiopsProgressTitleMessage"
id="xpack.aiops.progressTitle"
defaultMessage="Progress: {progress}% — {progressMessage}"
values={{ progress: Math.round(progress * 100), progressMessage }}
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem css={isRunning ? runningProgressBarStyles : undefined}>
<EuiProgress
aria-label={i18n.translate('xpack.aiops.progressAriaLabel', {
defaultMessage: 'Progress',
})}
value={Math.round(progress * 100)}
max={100}
size="m"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{children}
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ interface DocumentCountChartProps {
interval: number;
chartPointsSplitLabel: string;
isBrushCleared: boolean;
/* Timestamp for start of initial analysis */
autoAnalysisStart?: number;
}

const SPEC_ID = 'document_count';
Expand Down Expand Up @@ -101,6 +103,7 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
interval,
chartPointsSplitLabel,
isBrushCleared,
autoAnalysisStart,
}) => {
const { data, uiSettings, fieldFormats, charts } = useAiopsAppContext();

Expand Down Expand Up @@ -201,39 +204,6 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
timefilterUpdateHandler({ from, to });
};

const onElementClick: ElementClickListener = ([elementData]) => {
if (brushSelectionUpdateHandler === undefined) {
return;
}
const startRange = (elementData as XYChartElementEvent)[0].x;

const range = {
from: startRange,
to: startRange + interval,
};

if (viewMode === VIEW_MODE.ZOOM) {
timefilterUpdateHandler(range);
} else {
if (
typeof startRange === 'number' &&
originalWindowParameters === undefined &&
windowParameters === undefined &&
adjustedChartPoints !== undefined
) {
const wp = getWindowParameters(
startRange + interval / 2,
timeRangeEarliest,
timeRangeLatest + interval
);
const wpSnap = getSnappedWindowParameters(wp, snapTimestamps);
setOriginalWindowParameters(wpSnap);
setWindowParameters(wpSnap);
brushSelectionUpdateHandler(wpSnap, true);
}
}
};

const timeZone = getTimezone(uiSettings);

const [originalWindowParameters, setOriginalWindowParameters] = useState<
Expand All @@ -244,6 +214,69 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
WindowParameters | undefined
>();

const triggerAnalysis = useCallback(
(startRange: number) => {
const range = {
from: startRange,
to: startRange + interval,
};

if (viewMode === VIEW_MODE.ZOOM) {
timefilterUpdateHandler(range);
} else {
if (
typeof startRange === 'number' &&
originalWindowParameters === undefined &&
windowParameters === undefined &&
adjustedChartPoints !== undefined
) {
const wp = getWindowParameters(
startRange + interval / 2,
timeRangeEarliest,
timeRangeLatest + interval
);
const wpSnap = getSnappedWindowParameters(wp, snapTimestamps);
setOriginalWindowParameters(wpSnap);
setWindowParameters(wpSnap);
if (brushSelectionUpdateHandler !== undefined) {
brushSelectionUpdateHandler(wpSnap, true);
}
}
}
},
[
interval,
timeRangeEarliest,
timeRangeLatest,
snapTimestamps,
originalWindowParameters,
setWindowParameters,
brushSelectionUpdateHandler,
adjustedChartPoints,
timefilterUpdateHandler,
viewMode,
windowParameters,
]
);

const onElementClick: ElementClickListener = useCallback(
([elementData]) => {
if (brushSelectionUpdateHandler === undefined) {
return;
}
const startRange = (elementData as XYChartElementEvent)[0].x;

triggerAnalysis(startRange);
},
[triggerAnalysis, brushSelectionUpdateHandler]
);

useEffect(() => {
if (autoAnalysisStart !== undefined) {
triggerAnalysis(autoAnalysisStart);
}
}, [triggerAnalysis, autoAnalysisStart]);

useEffect(() => {
if (isBrushCleared && originalWindowParameters !== undefined) {
setOriginalWindowParameters(undefined);
Expand Down Expand Up @@ -351,7 +384,6 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
position={Position.Bottom}
showOverlappingTicks={true}
tickFormat={(value) => xAxisFormatter.convert(value)}
// temporary fix to reduce horizontal chart margin until fixed in Elastic Charts itself
labelFormat={useLegacyTimeAxis ? undefined : () => ''}
timeAxisLayerCount={useLegacyTimeAxis ? 0 : 2}
style={useLegacyTimeAxis ? {} : MULTILAYER_TIME_AXIS_STYLE}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface DocumentCountContentProps {
totalCount: number;
sampleProbability: number;
windowParameters?: WindowParameters;
incomingInitialAnalysisStart?: number;
}

export const DocumentCountContent: FC<DocumentCountContentProps> = ({
Expand All @@ -44,8 +45,12 @@ export const DocumentCountContent: FC<DocumentCountContentProps> = ({
totalCount,
sampleProbability,
windowParameters,
incomingInitialAnalysisStart,
}) => {
const [isBrushCleared, setIsBrushCleared] = useState(true);
const [initialAnalysisStart, setInitialAnalysisStart] = useState<number | undefined>(
incomingInitialAnalysisStart
);

useEffect(() => {
setIsBrushCleared(windowParameters === undefined);
Expand Down Expand Up @@ -95,6 +100,7 @@ export const DocumentCountContent: FC<DocumentCountContentProps> = ({

function clearSelection() {
setIsBrushCleared(true);
setInitialAnalysisStart(undefined);
clearSelectionHandler();
}

Expand Down Expand Up @@ -126,6 +132,7 @@ export const DocumentCountContent: FC<DocumentCountContentProps> = ({
interval={documentCountStats.interval}
chartPointsSplitLabel={documentCountStatsSplitLabel}
isBrushCleared={isBrushCleared}
autoAnalysisStart={initialAnalysisStart}
/>
)}
</>
Expand Down
Loading

0 comments on commit 03d4fe7

Please sign in to comment.