diff --git a/common/constants/shared.ts b/common/constants/shared.ts index cc7a5147b0..be63484cea 100644 --- a/common/constants/shared.ts +++ b/common/constants/shared.ts @@ -240,6 +240,7 @@ export const WAITING_TIME_ON_USER_ACTIONS = 300; export const VISUALIZATION_ERROR = { NO_DATA: 'No data found.', INVALID_DATA: 'Invalid visualization data', + NO_SERIES: 'Add a field to start', }; export const S3_DATASOURCE_TYPE = 'S3_DATASOURCE'; diff --git a/public/components/event_analytics/explorer/events_views/data_grid.tsx b/public/components/event_analytics/explorer/events_views/data_grid.tsx index b84e4bc646..56c9d5af9d 100644 --- a/public/components/event_analytics/explorer/events_views/data_grid.tsx +++ b/public/components/event_analytics/explorer/events_views/data_grid.tsx @@ -57,11 +57,11 @@ export function DataGrid(props: DataGridProps) { startTime, endTime, } = props; - const { getEvents } = useFetchEvents({ + const { fetchEvents } = useFetchEvents({ pplService, requestParams, }); - const storedSelectedColumns = + const selectedColumns = explorerFields.selectedFields.length > 0 ? explorerFields.selectedFields : DEFAULT_EMPTY_EXPLORER_FIELDS; @@ -70,44 +70,63 @@ export function DataGrid(props: DataGridProps) { const sortingFields: MutableRefObject = useRef([]); const pageFields = useRef([0, 100]); + const [data, setData] = useState(rows); + // setSort and setPage are used to change the query and send a direct request to get data const setSort = (sort: EuiDataGridSorting['columns']) => { sortingFields.current = sort; - redoQuery(startTime, endTime, rawQuery, timeStampField, sortingFields, pageFields, getEvents); + + redoQuery( + startTime, + endTime, + rawQuery, + timeStampField, + sortingFields, + pageFields, + fetchEvents, + setData + ); }; const setPage = (page: number[]) => { pageFields.current = page; - redoQuery(startTime, endTime, rawQuery, timeStampField, sortingFields, pageFields, getEvents); + const res = redoQuery( + startTime, + endTime, + rawQuery, + timeStampField, + sortingFields, + pageFields, + fetchEvents, + setData + ); + console.log(res); }; // creates the header for each column listing what that column is const dataGridColumns = useMemo(() => { - if (storedSelectedColumns.length > 0) { - const columns: EuiDataGridColumn[] = []; - storedSelectedColumns.map(({ name, type }) => { - if (name === 'timestamp') { - columns.push(DEFAULT_TIMESTAMP_COLUMN); - } else if (name === '_source') { - columns.push(DEFAULT_SOURCE_COLUMN); - } else { - columns.push({ - id: name, - display: name, - isSortable: true, // TODO: add functionality here based on type - }); - } - }); - return columns; - } - return []; - }, [storedSelectedColumns]); + const columns: EuiDataGridColumn[] = []; + selectedColumns.map(({ name, type }) => { + if (name === 'timestamp') { + columns.push(DEFAULT_TIMESTAMP_COLUMN); + } else if (name === '_source') { + columns.push(DEFAULT_SOURCE_COLUMN); + } else { + columns.push({ + id: name, + display: name, + isSortable: true, // TODO: add functionality here based on type + }); + } + }); + return columns; + }, [explorerFields]); // used for which columns are visible and their order const dataGridColumnVisibility = useMemo(() => { - if (storedSelectedColumns.length > 0) { + if (selectedColumns.length > 0) { const columns: string[] = []; - storedSelectedColumns.map(({ name }) => { + selectedColumns.map(({ name }) => { columns.push(name); }); return { @@ -119,7 +138,7 @@ export function DataGrid(props: DataGridProps) { } // default shown fields throw new Error('explorer data grid stored columns empty'); - }, [storedSelectedColumns]); + }, [explorerFields]); // sets the very first column, which is the button used for the flyout of each row const dataGridLeadingColumns = useMemo(() => { @@ -159,17 +178,17 @@ export function DataGrid(props: DataGridProps) { const dataGridCellRender = useCallback( ({ rowIndex, columnId }: { rowIndex: number; columnId: string }) => { const trueIndex = rowIndex % pageFields.current[1]; - if (trueIndex < rows.length) { + if (trueIndex < data.length) { if (columnId === '_source') { return ( - {Object.keys(rows[trueIndex]).map((key) => ( + {Object.keys(data[trueIndex]).map((key) => ( {key} - {rows[trueIndex][key]} + {data[trueIndex][key]} ))} @@ -177,13 +196,13 @@ export function DataGrid(props: DataGridProps) { ); } if (columnId === 'timestamp') { - return `${moment(rows[trueIndex][columnId]).format(DATE_DISPLAY_FORMAT)}`; + return `${moment(data[trueIndex][columnId]).format(DATE_DISPLAY_FORMAT)}`; } - return `${rows[trueIndex][columnId]}`; + return `${data[trueIndex][columnId]}`; } return null; }, - [rows, pageFields, explorerFields] + [data, rows, pageFields, explorerFields] ); // ** Pagination config @@ -212,10 +231,10 @@ export function DataGrid(props: DataGridProps) { () => ({ defaultHeight: { // if source is listed as a column, add extra space - lineCount: storedSelectedColumns.some((obj) => obj.name === '_source') ? 3 : 1, + lineCount: selectedColumns.some((obj) => obj.name === '_source') ? 3 : 1, }, }), - [storedSelectedColumns] + [explorerFields] ); // TODO: memoize the expensive table below diff --git a/public/components/event_analytics/explorer/explorer.scss b/public/components/event_analytics/explorer/explorer.scss index 70a79a3a57..63dfc2b087 100644 --- a/public/components/event_analytics/explorer/explorer.scss +++ b/public/components/event_analytics/explorer/explorer.scss @@ -15,7 +15,7 @@ } .mainContentTabs .euiResizableContainer { - height: calc(100vh - 298px); + height: calc(100vh - 194px); } .explorer-loading-spinner { diff --git a/public/components/event_analytics/explorer/explorer.tsx b/public/components/event_analytics/explorer/explorer.tsx index 02fc7f5ba0..dbdbcfc04d 100644 --- a/public/components/event_analytics/explorer/explorer.tsx +++ b/public/components/event_analytics/explorer/explorer.tsx @@ -569,7 +569,7 @@ export const Explorer = ({ explorerFields={explorerFields} timeStampField={queryRef.current![SELECTED_TIMESTAMP]} rawQuery={appBasedRef.current || queryRef.current![RAW_QUERY]} - totalHits={explorerData?.datarows?.length || 0} + totalHits={_.sum(countDistribution.data['count()'])} requestParams={requestParams} startTime={appLogEvents ? startTime : dateRange[0]} endTime={appLogEvents ? endTime : dateRange[1]} diff --git a/public/components/event_analytics/explorer/sidebar/sidebar.tsx b/public/components/event_analytics/explorer/sidebar/sidebar.tsx index a9b2a3eed9..71049987fd 100644 --- a/public/components/event_analytics/explorer/sidebar/sidebar.tsx +++ b/public/components/event_analytics/explorer/sidebar/sidebar.tsx @@ -14,7 +14,7 @@ import { } from '@elastic/eui'; import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; import { isEmpty } from 'lodash'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { batch, useDispatch } from 'react-redux'; import { AVAILABLE_FIELDS, SELECTED_FIELDS } from '../../../../../common/constants/explorer'; import { ExplorerFields, IExplorerFields, IField } from '../../../../../common/types/explorer'; @@ -54,6 +54,18 @@ export const Sidebar = (props: ISidebarProps) => { const [showFields, setShowFields] = useState(false); const [searchTerm, setSearchTerm] = useState(''); + // method to return the type of a field from its name + const getFieldTypes = (newFieldName: string) => { + let fieldType: string = ''; + explorerFields.availableFields.map((field) => { + if (field.name === newFieldName) fieldType = field.type; + }); + explorerFields.selectedFields.map((field) => { + if (field.name === newFieldName) fieldType = field.type; + }); + return fieldType; + }; + /** * Toggle fields between selected and unselected sets * @param fieldState all fields in store @@ -117,8 +129,24 @@ export const Sidebar = (props: ISidebarProps) => { [explorerFields, tabId] ); - const onDragEnd = ({}) => { - console.log('source, destination'); + const onDragEnd = ({ + destination, + source, + draggableId, + }: { + destination: any; + source: any; + draggableId: string; + }) => { + // check if the destination and source are the same area + if (destination.droppableId !== source.droppableId) { + // if dropped into the selected fields: add, if dropped into available: remove + if (destination.droppableId === 'SELECTED FIELDS') { + handleAddField({ name: draggableId, type: getFieldTypes(draggableId) }); + } else if (destination.droppableId === 'AVAILABLE FIELDS') { + handleRemoveField({ name: draggableId, type: getFieldTypes(draggableId) }); + } + } }; return ( diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.scss b/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.scss index 7025380fc7..8e0032e9f5 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.scss +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.scss @@ -158,6 +158,7 @@ $vis-editor-sidebar-min-width: 350px; #vis__mainContent .vis__leftPanel { overflow-y: unset; // unset default setting + margin-right: 8px; } .panelItem_button { @@ -168,6 +169,15 @@ $vis-editor-sidebar-min-width: 350px; align-items: center; } +.panelItem_box { + color: #5A6875; + display: grid; + grid-gap: 4px; + padding: 8px 8px 8px 8px; + background-color: #D6D9DD; + border-radius: 4px; +} + .field_text { text-overflow: ellipsis; overflow: hidden; diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_panel_fields.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_panel_fields.tsx index 4c7f666c54..669a314c8e 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_panel_fields.tsx +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_config_panel_fields.tsx @@ -12,6 +12,8 @@ import { EuiText, EuiTitle, EuiToolTip, + EuiFormRow, + EuiFormLabel, } from '@elastic/eui'; import { isArray, isEmpty, lowerCase } from 'lodash'; import { @@ -60,7 +62,7 @@ export const DataConfigPanelFields = ({ const { time_field: timeField, unit, interval } = dimensionSpan; - const tooltipIcon = ; + const tooltipIcon = ; const crossIcon = (index: number, configName: string) => ( -
- -

{sectionName}

-
- {infoToolTip(tooltipIcon, DATA_CONFIG_HINTS_INFO[`${sectionName}`])} -
- - {sectionName === GROUPBY && dimensionSpan && !isEmpty(timeField) && ( - - - handleServiceEdit(list.length - 1, GROUPBY, true)} - data-test-subj="viz-config-add-btn" - > - {`${SPAN}(${timeField[0]?.name}, ${interval} ${unit[0]?.value})`} - - - {crossIcon(-1, SPAN)} - - )} - - {isArray(list) && - list.map((obj: ConfigListEntry, index: number) => ( - + + <> +
+ {sectionName} + {infoToolTip(tooltipIcon, DATA_CONFIG_HINTS_INFO[`${sectionName}`])} +
+ +
+ {sectionName === GROUPBY && dimensionSpan && !isEmpty(timeField) && ( handleServiceEdit(index, sectionName, false)} + onClick={() => handleServiceEdit(list.length - 1, GROUPBY, true)} data-test-subj="viz-config-add-btn" > - {removeBacktick( - obj[CUSTOM_LABEL] || `${isAggregation ? obj.aggregation : ''} ${obj.label}` - )} + {`${SPAN}(${timeField[0]?.name}, ${interval} ${unit[0]?.value})`} - {isAggregation - ? infoToolTip(crossIcon(index, sectionName), DATA_CONFIG_HINTS_INFO[AGGREGATIONS]) - : crossIcon(index, sectionName)} + {crossIcon(-1, SPAN)} + + )} + {isArray(list) && + list.map((obj: ConfigListEntry, index: number) => ( + + + + handleServiceEdit(index, sectionName, false)} + data-test-subj="viz-config-add-btn" + > + {removeBacktick( + obj[CUSTOM_LABEL] || `${isAggregation ? obj.aggregation : ''} ${obj.label}` + )} + + + {isAggregation + ? infoToolTip( + crossIcon(index, sectionName), + DATA_CONFIG_HINTS_INFO[AGGREGATIONS] + ) + : crossIcon(index, sectionName)} + + + ))} + {!hideClickToAddButton(sectionName) && ( + + {addButtonText} + handleServiceAdd(sectionName)} + data-test-subj="viz-config-add-btn" + /> - - - ))} - {!hideClickToAddButton(sectionName) && ( - - {addButtonText} - handleServiceAdd(sectionName)} - data-test-subj="viz-config-add-btn" - /> - - )} - -
+ )} + + +
); }; diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.scss b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.scss index cac373f90a..6ca8a2e848 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.scss +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.scss @@ -39,4 +39,50 @@ &.showSecondary > .wizConfig__section { transform: translateX(-100%); } +} + +.vbConfig { + @include euiYScrollWithShadows; + + background: $euiColorLightestShade; + border-left: $euiBorderThin; + position: relative; + overflow-x: hidden; + + &__section { + width: 100%; + transition: transform $euiAnimSpeedNormal 0s $euiAnimSlightResistance; + } + + &__title { + padding: $euiSizeS; + padding-bottom: 0; + + &.showDivider { + border-bottom: 1px solid $euiColorLightShade; + } + } + + &__content { + padding: $euiSizeS; + } + + &__aggEditor { + padding: 0 $euiSizeM; + } + + &--secondary { + position: absolute; + top: 0; + left: 0; + padding: $euiSizeS; + + .visEditorAggParam--half { + margin: $euiSize 0; + } + } + + &.showSecondary > .vbConfig__section { + transform: translateX(-100%); + } } \ No newline at end of file diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx index e5374811ed..d55fedc850 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx @@ -16,6 +16,9 @@ import { EuiSpacer, EuiTitle, htmlIdGenerator, + EuiForm, + EuiFlexGroup, + EuiHorizontalRule, } from '@elastic/eui'; import { filter, isEmpty, isEqual } from 'lodash'; import { @@ -340,7 +343,7 @@ export const DataConfigPanelItem = ({ const selectedObj = isTimeStampSelected ? configList[SPAN] : configList[name][index]; const isAggregations = name === AGGREGATIONS; return ( - <> +
- +
); }; @@ -535,49 +538,59 @@ export const DataConfigPanelItem = ({ return isAddConfigClicked ? ( getCommonUI(selectedConfigItem.name) ) : ( - <> - -

Configuration

-
- - {visualizations.vis.name !== VIS_CHART_TYPES.Histogram ? ( - <> - {DataConfigPanelFields(getRenderFieldsObj(AGGREGATIONS))} - - {DataConfigPanelFields(getRenderFieldsObj(GROUPBY))} - - {(visualizations.vis.name === VIS_CHART_TYPES.Bar || - visualizations.vis.name === VIS_CHART_TYPES.HorizontalBar || - visualizations.vis.name === VIS_CHART_TYPES.Line) && ( - <>{DataConfigPanelFields(getRenderFieldsObj(BREAKDOWNS))} - )} - - ) : ( - <> - -

Bucket Size

-
- {getNumberField('bucketSize')} + +
+
+ + + +

Configuration

+
+
+
+
+ {visualizations.vis.name !== VIS_CHART_TYPES.Histogram ? ( +
+ + {DataConfigPanelFields(getRenderFieldsObj(AGGREGATIONS))} + + {DataConfigPanelFields(getRenderFieldsObj(GROUPBY))} + + {(visualizations.vis.name === VIS_CHART_TYPES.Bar || + visualizations.vis.name === VIS_CHART_TYPES.HorizontalBar || + visualizations.vis.name === VIS_CHART_TYPES.Line) && ( + <>{DataConfigPanelFields(getRenderFieldsObj(BREAKDOWNS))} + )} + +
+ ) : ( + <> + +

Bucket Size

+
+ {getNumberField('bucketSize')} - - -

Bucket Offset

-
- {getNumberField('bucketOffset')} - - )} - - - updateChart()} - size="s" - isDisabled={isEmpty(configList[AGGREGATIONS])} - > - Update chart - - - + + +

Bucket Offset

+
+ {getNumberField('bucketOffset')} + + )} +
+ + updateChart()} + size="s" + isDisabled={isEmpty(configList[AGGREGATIONS])} + > + Update chart + + +
+
+
); }; diff --git a/public/components/event_analytics/explorer/visualizations/index.tsx b/public/components/event_analytics/explorer/visualizations/index.tsx index f97db37f3f..0b3a6bea5b 100644 --- a/public/components/event_analytics/explorer/visualizations/index.tsx +++ b/public/components/event_analytics/explorer/visualizations/index.tsx @@ -93,16 +93,7 @@ export const ExplorerVisualizations = ({ paddingSize="none" className="vis__leftPanel" > -
- {!isMarkDown && ( -
- {renderDataConfigContainer()} -
- )} -
+ {!isMarkDown && <>{renderDataConfigContainer()}} , pageFields: MutableRefObject, - getEvents: any + fetchEvents: any, + setData: React.Dispatch> ) => { let finalQuery = ''; @@ -436,5 +437,8 @@ export const redoQuery = ( finalQuery = finalQuery + ` | head ${pageFields.current[1]} from ${pageFields.current[0] * pageFields.current[1]}`; - getEvents(finalQuery); + + fetchEvents({ query: finalQuery }, 'jdbc', (res: any) => { + setData(res.jsonData); + }); }; diff --git a/public/components/metrics/index.scss b/public/components/metrics/index.scss index 9f2d601982..abfeba3747 100644 --- a/public/components/metrics/index.scss +++ b/public/components/metrics/index.scss @@ -15,6 +15,6 @@ } .mainContentTabs .euiResizableContainer { - height: calc(100vh - 298px); + height: calc(100vh - 194px); } \ No newline at end of file diff --git a/public/components/visualizations/visualization.tsx b/public/components/visualizations/visualization.tsx index 6de22585ef..313f43cf7b 100644 --- a/public/components/visualizations/visualization.tsx +++ b/public/components/visualizations/visualization.tsx @@ -29,7 +29,7 @@ export const Visualization = ({ // Markdown, it does not depend on if there is data if (vis.id === VIS_CHART_TYPES.Text) return [true, '']; - if (isEmpty(series)) return [false, VISUALIZATION_ERROR.INVALID_DATA]; // series is required to any visualization type + if (isEmpty(series)) return [false, VISUALIZATION_ERROR.NO_SERIES]; // series is required to any visualization type // bars, pie if (dimensions.length < 1 && isEmpty(span)) return [false, VISUALIZATION_ERROR.INVALID_DATA];