Skip to content

Commit

Permalink
[Serverless][DataUsage] Data usage UX/API updates (elastic#203465)
Browse files Browse the repository at this point in the history
  • Loading branch information
ashokaditya authored Dec 12, 2024
1 parent 7803168 commit b433119
Show file tree
Hide file tree
Showing 24 changed files with 878 additions and 232 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export const isDefaultMetricType = (metricType: string) =>
DEFAULT_METRIC_TYPES.includes(metricType);

export const METRIC_TYPE_API_VALUES_TO_UI_OPTIONS_MAP = Object.freeze<Record<MetricTypes, string>>({
storage_retained: 'Data Retained in Storage',
ingest_rate: 'Data Ingested',
storage_retained: 'Data Retained in Storage',
search_vcu: 'Search VCU',
ingest_vcu: 'Ingest VCU',
ml_vcu: 'ML VCU',
Expand All @@ -40,8 +40,8 @@ export const METRIC_TYPE_API_VALUES_TO_UI_OPTIONS_MAP = Object.freeze<Record<Met
});

export const METRIC_TYPE_UI_OPTIONS_VALUES_TO_API_MAP = Object.freeze<Record<string, MetricTypes>>({
'Data Retained in Storage': 'storage_retained',
'Data Ingested': 'ingest_rate',
'Data Retained in Storage': 'storage_retained',
'Search VCU': 'search_vcu',
'Ingest VCU': 'ingest_vcu',
'ML VCU': 'ml_vcu',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { isDateRangeValid } from './utils';
describe('isDateRangeValid', () => {
describe('Valid ranges', () => {
it.each([
['both start and end date is `now`', { start: 'now', end: 'now' }],
['start date is `now-10s` and end date is `now`', { start: 'now-10s', end: 'now' }],
['bounded within the min and max date range', { start: 'now-8d', end: 'now-4s' }],
])('should return true if %s', (_, { start, end }) => {
Expand All @@ -20,8 +19,10 @@ describe('isDateRangeValid', () => {

describe('Invalid ranges', () => {
it.each([
['both start and end date is `now`', { start: 'now', end: 'now' }],
['starts before the min date', { start: 'now-11d', end: 'now-5s' }],
['ends after the max date', { start: 'now-9d', end: 'now+2s' }],
['ends after the max date in seconds', { start: 'now-9d', end: 'now+2s' }],
['ends after the max date in days', { start: 'now-6d', end: 'now+6d' }],
[
'end date is before the start date even when both are within min and max date range',
{ start: 'now-3s', end: 'now-10s' },
Expand Down
3 changes: 2 additions & 1 deletion x-pack/platform/plugins/private/data_usage/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const DEFAULT_DATE_RANGE_OPTIONS = Object.freeze({
recentlyUsedDateRanges: [],
});

export type ParsedDate = ReturnType<typeof momentDateParser>;
export const momentDateParser = (date: string) => dateMath.parse(date);
export const transformToUTCtime = ({
start,
Expand Down Expand Up @@ -50,6 +51,6 @@ export const isDateRangeValid = ({ start, end }: { start: string; end: string })
return (
startDate.isSameOrAfter(minDate, 's') &&
endDate.isSameOrBefore(maxDate, 's') &&
startDate <= endDate
startDate.isBefore(endDate, 's')
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { DataUsageMetrics } from './data_usage_metrics';
import { useGetDataUsageMetrics } from '../../hooks/use_get_usage_metrics';
import { useGetDataUsageDataStreams } from '../../hooks/use_get_data_streams';
import { coreMock as mockCore } from '@kbn/core/public/mocks';
import { mockUseKibana, generateDataStreams } from '../mocks';

jest.mock('../../utils/use_breadcrumbs', () => {
return {
Expand Down Expand Up @@ -60,60 +61,10 @@ jest.mock('@kbn/kibana-react-plugin/public', () => {
const original = jest.requireActual('@kbn/kibana-react-plugin/public');
return {
...original,
useKibana: () => ({
services: {
uiSettings: {
get: jest.fn().mockImplementation((key) => {
const get = (k: 'dateFormat' | 'timepicker:quickRanges') => {
const x = {
dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
'timepicker:quickRanges': [
{
from: 'now/d',
to: 'now/d',
display: 'Today',
},
{
from: 'now/w',
to: 'now/w',
display: 'This week',
},
{
from: 'now-15m',
to: 'now',
display: 'Last 15 minutes',
},
{
from: 'now-30m',
to: 'now',
display: 'Last 30 minutes',
},
{
from: 'now-1h',
to: 'now',
display: 'Last 1 hour',
},
{
from: 'now-24h',
to: 'now',
display: 'Last 24 hours',
},
{
from: 'now-7d',
to: 'now',
display: 'Last 7 days',
},
],
};
return x[k];
};
return get(key);
}),
},
},
}),
useKibana: () => mockUseKibana,
};
});

const mockUseGetDataUsageMetrics = useGetDataUsageMetrics as jest.Mock;
const mockUseGetDataUsageDataStreams = useGetDataUsageDataStreams as jest.Mock;
const mockServices = mockCore.createStart();
Expand All @@ -131,13 +82,6 @@ const getBaseMockedDataUsageMetrics = () => ({
refetch: jest.fn(),
});

const generateDataStreams = (count: number) => {
return Array.from({ length: count }, (_, i) => ({
name: `.ds-${i}`,
storageSizeBytes: 1024 ** 2 * (22 / 7),
}));
};

describe('DataUsageMetrics', () => {
let user: UserEvent;
const testId = 'test';
Expand Down Expand Up @@ -228,14 +172,14 @@ describe('DataUsageMetrics', () => {
expect(toggleFilterButton).toHaveTextContent('Data streams10');
await user.click(toggleFilterButton);
const allFilterOptions = getAllByTestId('dataStreams-filter-option');
// deselect 9 options
for (let i = 0; i < allFilterOptions.length; i++) {
// deselect 3 options
for (let i = 0; i < 3; i++) {
await user.click(allFilterOptions[i]);
}

expect(toggleFilterButton).toHaveTextContent('Data streams1');
expect(toggleFilterButton).toHaveTextContent('Data streams7');
expect(within(toggleFilterButton).getByRole('marquee').getAttribute('aria-label')).toEqual(
'1 active filters'
'7 active filters'
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,9 @@ import { PLUGIN_NAME } from '../../translations';
import { useGetDataUsageMetrics } from '../../hooks/use_get_usage_metrics';
import { useGetDataUsageDataStreams } from '../../hooks/use_get_data_streams';
import { useDataUsageMetricsUrlParams } from '../hooks/use_charts_url_params';
import {
DEFAULT_DATE_RANGE_OPTIONS,
transformToUTCtime,
isDateRangeValid,
} from '../../../common/utils';
import { DEFAULT_DATE_RANGE_OPTIONS, transformToUTCtime } from '../../../common/utils';
import { useDateRangePicker } from '../hooks/use_date_picker';
import { ChartFilters, ChartFiltersProps } from './filters/charts_filters';
import { ChartsFilters, ChartsFiltersProps } from './filters/charts_filters';
import { ChartsLoading } from './charts_loading';
import { NoDataCallout } from './no_data_callout';
import { useTestIdGenerator } from '../../hooks/use_test_id_generator';
Expand Down Expand Up @@ -114,16 +110,8 @@ export const DataUsageMetrics = memo(
}));
}, [metricTypesFromUrl, dataStreamsFromUrl, startDateFromUrl, endDateFromUrl]);

const { dateRangePickerState, onRefreshChange, onTimeChange } = useDateRangePicker();

const isValidDateRange = useMemo(
() =>
isDateRangeValid({
start: dateRangePickerState.startDate,
end: dateRangePickerState.endDate,
}),
[dateRangePickerState.endDate, dateRangePickerState.startDate]
);
const { dateRangePickerState, isValidDateRange, onRefreshChange, onTimeChange } =
useDateRangePicker();

const enableFetchUsageMetricsData = useMemo(
() =>
Expand Down Expand Up @@ -187,8 +175,10 @@ export const DataUsageMetrics = memo(
[setMetricsFilters]
);

const filterOptions: ChartFiltersProps['filterOptions'] = useMemo(() => {
const dataStreamsOptions = dataStreams?.reduce<Record<string, number>>((acc, ds) => {
const filterOptions: ChartsFiltersProps['filterOptions'] = useMemo(() => {
const dataStreamsOptions = dataStreams?.reduce<
Required<ChartsFiltersProps['filterOptions']['dataStreams']>['appendOptions']
>((acc, ds) => {
acc[ds.name] = ds.storageSizeBytes;
return acc;
}, {});
Expand Down Expand Up @@ -239,10 +229,11 @@ export const DataUsageMetrics = memo(
return (
<EuiFlexGroup alignItems="flexStart" direction="column" data-test-subj={getTestId()}>
<FlexItemWithCss>
<ChartFilters
<ChartsFilters
dateRangePickerState={dateRangePickerState}
isDataLoading={isFetchingDataStreams}
isUpdateDisabled={!enableFetchUsageMetricsData}
isValidDateRange={isValidDateRange}
onClick={refetchDataUsageMetrics}
onRefresh={onRefresh}
onRefreshChange={onRefreshChange}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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 React from 'react';
import { TestProvider } from '../../../../common/test_utils';
import { render, type RenderResult } from '@testing-library/react';
import userEvent, { type UserEvent } from '@testing-library/user-event';
import { ChartsFilter, type ChartsFilterProps } from './charts_filter';
import { FilterName } from '../../hooks';
import { mockUseKibana, generateDataStreams } from '../../mocks';

const mockUseLocation = jest.fn(() => ({ pathname: '/' }));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: () => mockUseLocation(),
useHistory: jest.fn().mockReturnValue({
push: jest.fn(),
listen: jest.fn(),
location: {
search: '',
},
}),
}));

jest.mock('@kbn/kibana-react-plugin/public', () => {
const original = jest.requireActual('@kbn/kibana-react-plugin/public');
return {
...original,
useKibana: () => mockUseKibana,
};
});

describe('Charts Filters', () => {
let user: UserEvent;
const testId = 'test';
const testIdFilter = `${testId}-filter`;

const defaultProps = {
filterOptions: {
filterName: 'dataStreams' as FilterName,
isFilterLoading: false,
appendOptions: {},
selectedOptions: [],
options: generateDataStreams(8).map((ds) => ds.name),
onChangeFilterOptions: jest.fn(),
},
};

let renderComponent: (props: ChartsFilterProps) => RenderResult;

beforeAll(() => {
jest.useFakeTimers();
});

afterAll(() => {
jest.useRealTimers();
});

beforeEach(() => {
jest.clearAllMocks();
renderComponent = (props: ChartsFilterProps) =>
render(
<TestProvider>
<ChartsFilter data-test-subj={testIdFilter} {...props} />
</TestProvider>
);
user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime, pointerEventsCheck: 0 });
});

it('renders data streams filter with all options selected', async () => {
const { getByTestId, getAllByTestId } = renderComponent(defaultProps);
expect(getByTestId(`${testIdFilter}-dataStreams-popoverButton`)).toBeTruthy();

const filterButton = getByTestId(`${testIdFilter}-dataStreams-popoverButton`);
expect(filterButton).toBeTruthy();
await user.click(filterButton);
const allFilterOptions = getAllByTestId('dataStreams-filter-option');

// checked options
const checkedOptions = allFilterOptions.filter(
(option) => option.getAttribute('aria-checked') === 'true'
);
expect(checkedOptions).toHaveLength(8);
});

it('renders data streams filter with 50 options selected when more than 50 items in the filter', async () => {
const { getByTestId } = renderComponent({
...defaultProps,
filterOptions: {
...defaultProps.filterOptions,
options: generateDataStreams(55).map((ds) => ds.name),
},
});
expect(getByTestId(`${testIdFilter}-dataStreams-popoverButton`)).toBeTruthy();

const toggleFilterButton = getByTestId(`${testIdFilter}-dataStreams-popoverButton`);
expect(toggleFilterButton).toBeTruthy();
expect(toggleFilterButton).toHaveTextContent('Data streams50');
expect(
toggleFilterButton.querySelector('.euiNotificationBadge')?.getAttribute('aria-label')
).toBe('50 active filters');
});

it('renders data streams filter with no options selected and select all is disabled', async () => {
const { getByTestId, queryByTestId } = renderComponent({
...defaultProps,
filterOptions: {
...defaultProps.filterOptions,
options: [],
},
});
expect(getByTestId(`${testIdFilter}-dataStreams-popoverButton`)).toBeTruthy();

const filterButton = getByTestId(`${testIdFilter}-dataStreams-popoverButton`);
expect(filterButton).toBeTruthy();
await user.click(filterButton);
expect(queryByTestId('dataStreams-filter-option')).toBeFalsy();
expect(getByTestId('dataStreams-group-label')).toBeTruthy();
expect(getByTestId(`${testIdFilter}-dataStreams-selectAllButton`)).toBeDisabled();
});
});
Loading

0 comments on commit b433119

Please sign in to comment.