Skip to content

Commit

Permalink
feat(investigate): add basic esql widget back (elastic#189092)
Browse files Browse the repository at this point in the history
Related to elastic#189091


This PR decouples the NotesWidget and EsqlWidget from the previous
AddWidgetUI, so we can have them both on the page. It's a step forward
the expected design. There is some missing element, like the
visualization selector which I'll add later.
I've also removed the global kuery filter as it is not on the design.

Things not implemented in this PR:
- edit widget
- date range filter (using default 15min from the page)
- lot of cleanup of unused feature

Screenshots |
-- |

![image](https://github.com/user-attachments/assets/791ef79c-1bf8-42b9-87a4-de9fd3f07986)

![image](https://github.com/user-attachments/assets/a326a0ea-a008-4fa7-911f-ea52de318e23)


Testing:
- Run some data forge scripts: `node x-pack/scripts/data_forge.js
--events-per-cycle 50 --lookback now-3d --dataset fake_stack
--install-kibana-assets --kibana-url http://localhost:5601/kibana`
- Go to http://localhost:5601/kibana/app/investigate/new#/
- Create an observation chart based on this esql query: `FROM
kbn-data-forge-fake_stack.admin-console-* | keep @timestamp,
http.response.* | LIMIT 103`

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Panagiota Mitsopoulou <[email protected]>
  • Loading branch information
3 people authored Jul 29, 2024
1 parent 5a09ec9 commit 829d22c
Show file tree
Hide file tree
Showing 23 changed files with 249 additions and 960 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ export interface GlobalWidgetParameters {
from: string;
to: string;
};
query: {
query: string;
language: 'kuery';
};
filters: Filter[];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,6 @@ function useInvestigationWithoutContext({
id: v4(),
globalWidgetParameters: {
filters: [],
query: {
language: 'kuery',
query: '',
},
timeRange: {
from,
to,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import { type BoolQuery, buildEsQuery } from '@kbn/es-query';
import type { GlobalWidgetParameters } from '../../common/types';

export function getEsFilterFromGlobalParameters({
query,
filters,
timeRange,
}: Partial<GlobalWidgetParameters>): { bool: BoolQuery } {
const esFilter = buildEsQuery(undefined, query ?? [], filters ?? []);
const esFilter = buildEsQuery(undefined, [], filters ?? []);

if (timeRange) {
esFilter.bool.filter.push({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import { Meta, StoryObj } from '@storybook/react';
import moment from 'moment';
import React from 'react';
import { InvestigationRevision } from '@kbn/investigate-plugin/common';
import { AddWidgetUI as Component } from '.';
import { AddNoteUI as Component } from '.';
import { KibanaReactStorybookDecorator } from '../../../.storybook/storybook_decorator';

interface Args {
Expand All @@ -30,26 +29,16 @@ export default meta;
const defaultStory: Story = {
args: {
props: {
start: moment().subtract(15, 'minutes'),
end: moment(),
onWidgetAdd: async () => {},
revision: {
items: [],
} as unknown as InvestigationRevision,
user: {
username: 'johndoe',
full_name: 'John Doe',
},
filters: [],
query: {
language: 'kuery',
query: '',
},
timeRange: {
from: moment().subtract(15, 'minutes').toISOString(),
to: moment().toISOString(),
},
workflowBlocks: [],
},
},
render: function Render(args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import type { AuthenticatedUser } from '@kbn/core/public';
import type { GlobalWidgetParameters, OnWidgetAdd } from '@kbn/investigate-plugin/public';
import React from 'react';
import { NoteWidgetControl } from '../note_widget_control';

type AddWidgetUIProps = {
user: Pick<AuthenticatedUser, 'full_name' | 'username'>;
onWidgetAdd: OnWidgetAdd;
} & GlobalWidgetParameters;

export function AddNoteUI({ user, onWidgetAdd }: AddWidgetUIProps) {
return (
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem grow={false}>
<NoteWidgetControl user={user} onWidgetAdd={onWidgetAdd} />
</EuiFlexItem>
</EuiFlexGroup>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,26 @@
* 2.0.
*/
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import {
InvestigateWidgetColumnSpan,
InvestigateWidgetCreate,
WorkflowBlock,
} from '@kbn/investigate-plugin/public';
import { css } from '@emotion/css';
import type { DataView } from '@kbn/data-views-plugin/common';
import type { ESQLColumn, ESQLRow } from '@kbn/es-types';
import {
createEsqlWidget,
ESQL_WIDGET_NAME,
GlobalWidgetParameters,
InvestigateWidgetColumnSpan,
InvestigateWidgetCreate,
OnWidgetAdd,
} from '@kbn/investigate-plugin/public';
import type { Suggestion } from '@kbn/lens-plugin/public';
import { useAbortableAsync } from '@kbn/observability-ai-assistant-plugin/public';
import { noop } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react';
import type { ESQLColumn, ESQLRow } from '@kbn/es-types';
import { css } from '@emotion/css';
import type { DataView } from '@kbn/data-views-plugin/common';
import { useKibana } from '../../hooks/use_kibana';
import { getEsFilterFromOverrides } from '../../utils/get_es_filter_from_overrides';
import { getDateHistogramResults } from '../../widgets/esql_widget/get_date_histogram_results';
import { EsqlWidget } from '../../widgets/esql_widget/register_esql_widget';
import { SuggestVisualizationList } from '../suggest_visualization_list';
import { ErrorMessage } from '../error_message';
import { getDateHistogramResults } from '../../widgets/esql_widget/get_date_histogram_results';
import { SuggestVisualizationList } from '../suggest_visualization_list';

function getWidgetFromSuggestion({
query,
Expand Down Expand Up @@ -67,6 +63,7 @@ function PreviewContainer({ children }: { children: React.ReactNode }) {
alignItems="center"
justifyContent="center"
className={css`
padding: 24px 0px 24px 0px;
width: 100%;
overflow: auto;
> div {
Expand All @@ -84,7 +81,6 @@ export function EsqlWidgetPreview({
onWidgetAdd,
filters,
timeRange,
query,
}: {
esqlQuery: string;
onWidgetAdd: OnWidgetAdd;
Expand All @@ -97,9 +93,8 @@ export function EsqlWidgetPreview({
return getEsFilterFromOverrides({
filters,
timeRange,
query,
});
}, [filters, timeRange, query]);
}, [filters, timeRange]);

const [selectedSuggestion, setSelectedSuggestion] = useState<Suggestion | undefined>(undefined);

Expand All @@ -121,7 +116,7 @@ export function EsqlWidgetPreview({

const dateHistoResponse = useAbortableAsync(
({ signal }) => {
if (!queryResult.value || queryResult.loading || !selectedSuggestion) {
if (!queryResult.value?.query?.values || queryResult.loading || !selectedSuggestion) {
return undefined;
}
return getDateHistogramResults({
Expand All @@ -137,16 +132,6 @@ export function EsqlWidgetPreview({
[queryResult, esql, filter, esqlQuery, selectedSuggestion, timeRange]
);

const fakeRenderApi = useMemo(() => {
return {
blocks: {
publish: (_blocks: WorkflowBlock[]) => {
return noop;
},
},
};
}, []);

const [displayedProps, setDisplayedProps] = useState<
{
error: Error | undefined;
Expand Down Expand Up @@ -216,7 +201,6 @@ export function EsqlWidgetPreview({
<EuiFlexItem grow={false}>
<PreviewContainer>
<EsqlWidget
blocks={fakeRenderApi.blocks}
suggestion={selectedSuggestion}
columns={displayedProps.value.columns}
allColumns={displayedProps.value.allColumns}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiPanel, EuiTitle } from '@elastic/eui';
import { css } from '@emotion/css';
import { TextBasedLangEditor } from '@kbn/esql/public';
import { i18n } from '@kbn/i18n';
import { GlobalWidgetParameters, OnWidgetAdd } from '@kbn/investigate-plugin/public';
import React from 'react';
import { EsqlWidgetPreview } from './esql_widget_preview';

type Props = {
onWidgetAdd: OnWidgetAdd;
} & GlobalWidgetParameters;

const emptyPreview = css`
padding: 36px 0px 36px 0px;
`;

export function AddObservationUI({ onWidgetAdd, timeRange, filters }: Props) {
const [isOpen, setIsOpen] = React.useState(false);

const [isExpanded, setIsExpanded] = React.useState(false);
const [query, setQuery] = React.useState({ esql: '' });
const [submittedQuery, setSubmittedQuery] = React.useState({ esql: '' });
const [isPreviewOpen, setIsPreviewOpen] = React.useState(false);

const resetState = () => {
setIsExpanded(false);
setIsPreviewOpen(false);
setQuery({ esql: '' });
setSubmittedQuery({ esql: '' });
};

if (!isOpen) {
return (
<EuiFlexGroup gutterSize="s" direction="row" alignItems="flexEnd">
<EuiFlexItem grow={true}>
<EuiButton
data-test-subj="investigateAppAddObservationUIAddAnObservationChartButton"
iconType="plusInCircle"
onClick={() => setIsOpen(true)}
>
{i18n.translate(
'xpack.investigateApp.addObservationUI.addAnObservationChartButtonLabel',
{ defaultMessage: 'Add an observation chart' }
)}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
);
}

return (
<EuiPanel paddingSize="l" grow={false}>
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexItem grow={true}>
<EuiTitle size="s">
<h3>
{i18n.translate(
'xpack.investigateApp.addObservationUI.h2.addAnObservationChartLabel',
{ defaultMessage: 'Add an observation chart' }
)}
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<EuiPanel color="subdued" hasShadow={false}>
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexItem>
<TextBasedLangEditor
query={query}
onTextLangQueryChange={setQuery}
onTextLangQuerySubmit={async (nextSubmittedQuery) => {
if (nextSubmittedQuery) {
setSubmittedQuery(nextSubmittedQuery);
setIsPreviewOpen(true);
}
}}
errors={undefined}
warning={undefined}
expandCodeEditor={(expanded: boolean) => {
setIsExpanded(() => expanded);
}}
isCodeEditorExpanded={isExpanded}
hideMinimizeButton={false}
editorIsInline={false}
hideRunQueryText
isLoading={false}
disableSubmitAction
isDisabled={false}
hideTimeFilterInfo
/>
</EuiFlexItem>

{!isPreviewOpen ? (
<EuiFlexGroup
direction="column"
alignItems="center"
gutterSize="l"
className={emptyPreview}
>
<EuiFlexItem grow={false}>
<EuiIcon type="image" size="xxl" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<p>
{i18n.translate(
'xpack.investigateApp.addObservationUI.p.selectADataSourceLabel',
{ defaultMessage: 'Select a data source to generate a preview chart' }
)}
</p>
</EuiFlexItem>
</EuiFlexGroup>
) : (
<EsqlWidgetPreview
filters={filters}
esqlQuery={submittedQuery.esql}
timeRange={timeRange}
onWidgetAdd={(widget) => {
resetState();
return onWidgetAdd(widget);
}}
/>
)}
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButton
color="text"
data-test-subj="investigateAppAddObservationUICancelButton"
onClick={() => {
resetState();
setIsOpen(false);
}}
>
{i18n.translate('xpack.investigateApp.addObservationUI.cancelButtonLabel', {
defaultMessage: 'Cancel',
})}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
</EuiPanel>
);
}
Loading

0 comments on commit 829d22c

Please sign in to comment.