Skip to content

Commit

Permalink
[Cases] Show the Average time to close stat in the all cases page (#1…
Browse files Browse the repository at this point in the history
  • Loading branch information
academo authored May 16, 2022
1 parent 059f09d commit 503b469
Show file tree
Hide file tree
Showing 16 changed files with 469 additions and 121 deletions.
13 changes: 10 additions & 3 deletions x-pack/plugins/cases/public/api/__mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@
* 2.0.
*/

import { CasesFindRequest } from '../../../common/api';
import { CasesFindRequest, CasesMetricsRequest } from '../../../common/api';
import { HTTPService } from '..';
import { casesStatus } from '../../containers/mock';
import { CasesStatus } from '../../containers/types';
import { casesMetrics, casesStatus } from '../../containers/mock';
import { CasesMetrics, CasesStatus } from '../../containers/types';

export const getCasesStatus = async ({
http,
signal,
query,
}: HTTPService & { query: CasesFindRequest }): Promise<CasesStatus> => Promise.resolve(casesStatus);

export const getCasesMetrics = async ({
http,
signal,
query,
}: HTTPService & { query: CasesMetricsRequest }): Promise<CasesMetrics> =>
Promise.resolve(casesMetrics);
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ import { waitForComponentToUpdate } from '../../common/test_utils';
import { useCreateAttachments } from '../../containers/use_create_attachments';
import { useGetTags } from '../../containers/use_get_tags';
import { useGetReporters } from '../../containers/use_get_reporters';
import { useGetCasesMetrics } from '../../containers/use_get_cases_metrics';

jest.mock('../../containers/use_create_attachments');
jest.mock('../../containers/use_bulk_update_case');
jest.mock('../../containers/use_delete_cases');
jest.mock('../../containers/use_get_cases');
jest.mock('../../containers/use_get_cases_status');
jest.mock('../../containers/use_get_cases_metrics');
jest.mock('../../containers/use_get_action_license');
jest.mock('../../containers/use_get_tags');
jest.mock('../../containers/use_get_reporters');
Expand All @@ -55,6 +57,7 @@ jest.mock('../app/use_available_owners', () => ({
const useDeleteCasesMock = useDeleteCases as jest.Mock;
const useGetCasesMock = useGetCases as jest.Mock;
const useGetCasesStatusMock = useGetCasesStatus as jest.Mock;
const useGetCasesMetricsMock = useGetCasesMetrics as jest.Mock;
const useUpdateCasesMock = useUpdateCases as jest.Mock;
const useGetActionLicenseMock = useGetActionLicense as jest.Mock;
const useGetTagsMock = useGetTags as jest.Mock;
Expand Down Expand Up @@ -118,6 +121,12 @@ describe('AllCasesListGeneric', () => {
isLoading: false,
};

const defaultCasesMetrics = {
mttr: 5,
isLoading: false,
fetchCasesMetrics: jest.fn(),
};

const defaultUpdateCases = {
isUpdated: false,
isLoading: false,
Expand Down Expand Up @@ -157,6 +166,7 @@ describe('AllCasesListGeneric', () => {
useGetCasesMock.mockReturnValue(defaultGetCases);
useDeleteCasesMock.mockReturnValue(defaultDeleteCases);
useGetCasesStatusMock.mockReturnValue(defaultCasesStatus);
useGetCasesMetricsMock.mockReturnValue(defaultCasesMetrics);
useGetActionLicenseMock.mockReturnValue(defaultActionLicense);
useGetTagsMock.mockReturnValue({ tags: ['coke', 'pepsi'], fetchTags: jest.fn() });
useGetReportersMock.mockReturnValue({
Expand Down Expand Up @@ -342,6 +352,15 @@ describe('AllCasesListGeneric', () => {
});
});

it('should render the case stats', () => {
const wrapper = mount(
<TestProviders>
<AllCasesList />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="cases-count-stats"]')).toBeTruthy();
});

it.skip('Bulk delete', async () => {
useGetCasesMock.mockReturnValue({
...defaultGetCases,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { EuiBasicTableOnChange } from './types';
import { CasesTable } from './table';
import { useConnectors } from '../../containers/configure/use_connectors';
import { useCasesContext } from '../cases_context/use_cases_context';
import { CasesMetrics } from './cases_metrics';

const ProgressLoader = styled(EuiProgress)`
${({ $isShow }: { $isShow: boolean }) =>
Expand Down Expand Up @@ -56,6 +57,7 @@ export const AllCasesList = React.memo<AllCasesListProps>(
const { owner, userCanCrud } = useCasesContext();
const hasOwner = !!owner.length;
const availableSolutions = useAvailableCasesOwners();
const [refresh, setRefresh] = useState(0);

const firstAvailableStatus = head(difference(caseStatuses, hiddenStatuses));
const initialFilterOptions = {
Expand Down Expand Up @@ -104,8 +106,13 @@ export const AllCasesList = React.memo<AllCasesListProps>(
const refreshCases = useCallback(
(dataRefresh = true) => {
deselectCases();
if (dataRefresh) refetchCases();
if (doRefresh) doRefresh();
if (dataRefresh) {
refetchCases();
setRefresh((currRefresh: number) => currRefresh + 1);
}
if (doRefresh) {
doRefresh();
}
if (filterRefetch.current != null) {
filterRefetch.current();
}
Expand Down Expand Up @@ -206,6 +213,7 @@ export const AllCasesList = React.memo<AllCasesListProps>(
className="essentialAnimation"
$isShow={(isCasesLoading || isLoading) && !isDataEmpty}
/>
<CasesMetrics refresh={refresh} />
<CasesTableFilters
countClosedCases={data.countClosedCases}
countOpenCases={data.countOpenCases}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 { within } from '@testing-library/dom';
import React from 'react';
import { AppMockRenderer, createAppMockRenderer } from '../../common/mock';
import { useGetCasesMetrics } from '../../containers/use_get_cases_metrics';
import { useGetCasesStatus } from '../../containers/use_get_cases_status';
import { CasesMetrics } from './cases_metrics';

jest.mock('../../containers/use_get_cases_metrics');
jest.mock('../../containers/use_get_cases_status');

const useGetCasesMetricsMock = useGetCasesMetrics as jest.Mock;
const useGetCasesStatusMock = useGetCasesStatus as jest.Mock;

describe('Cases metrics', () => {
useGetCasesStatusMock.mockReturnValue({
countOpenCases: 2,
countInProgressCases: 3,
countClosedCases: 4,
isLoading: false,
fetchCasesStatus: jest.fn(),
});
useGetCasesMetricsMock.mockReturnValue({
// 600 seconds = 10m
mttr: 600,
isLoading: false,
fetchCasesMetrics: jest.fn(),
});

let appMockRenderer: AppMockRenderer;

beforeEach(() => {
appMockRenderer = createAppMockRenderer();
});

it('renders the correct stats', () => {
const result = appMockRenderer.render(<CasesMetrics refresh={1} />);
expect(result.getByTestId('cases-metrics-stats')).toBeTruthy();
expect(within(result.getByTestId('openStatsHeader')).getByText(2)).toBeTruthy();
expect(within(result.getByTestId('inProgressStatsHeader')).getByText(3)).toBeTruthy();
expect(within(result.getByTestId('closedStatsHeader')).getByText(4)).toBeTruthy();
expect(within(result.getByTestId('mttrStatsHeader')).getByText('10m')).toBeTruthy();
});
});
119 changes: 119 additions & 0 deletions x-pack/plugins/cases/public/components/all_cases/cases_metrics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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, { FunctionComponent, useEffect, useMemo } from 'react';
import {
EuiDescriptionList,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiLoadingSpinner,
EuiToolTip,
} from '@elastic/eui';
import styled, { css } from 'styled-components';
import prettyMilliseconds from 'pretty-ms';
import { CaseStatuses } from '../../../common/api';
import { useGetCasesStatus } from '../../containers/use_get_cases_status';
import { StatusStats } from '../status/status_stats';
import { useGetCasesMetrics } from '../../containers/use_get_cases_metrics';
import { ATTC_DESCRIPTION, ATTC_STAT } from './translations';

interface CountProps {
refresh?: number;
}
const MetricsFlexGroup = styled.div`
${({ theme }) => css`
.euiFlexGroup {
border: ${theme.eui.euiBorderThin};
border-radius: ${theme.eui.euiBorderRadius};
margin: 0 0 ${theme.eui.euiSizeL} 0;
}
@media only screen and (max-width: ${theme.eui.euiBreakpoints.s}) {
.euiFlexGroup {
padding: ${theme.eui.euiSizeM};
}
}
`}
`;

export const CasesMetrics: FunctionComponent<CountProps> = ({ refresh }) => {
const {
countOpenCases,
countInProgressCases,
countClosedCases,
isLoading: isCasesStatusLoading,
fetchCasesStatus,
} = useGetCasesStatus();

const { mttr, isLoading: isCasesMetricsLoading, fetchCasesMetrics } = useGetCasesMetrics();

const mttrValue = useMemo(
() => (mttr ? prettyMilliseconds(mttr * 1000, { compact: true, verbose: false }) : '-'),
[mttr]
);

useEffect(() => {
if (refresh != null) {
fetchCasesStatus();
fetchCasesMetrics();
}
}, [fetchCasesMetrics, fetchCasesStatus, refresh]);

return (
<MetricsFlexGroup>
<EuiFlexGroup responsive={true} data-test-subj="cases-metrics-stats">
<EuiFlexItem grow={true}>
<StatusStats
dataTestSubj="openStatsHeader"
caseCount={countOpenCases}
caseStatus={CaseStatuses.open}
isLoading={isCasesStatusLoading}
/>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<StatusStats
dataTestSubj="inProgressStatsHeader"
caseCount={countInProgressCases}
caseStatus={CaseStatuses['in-progress']}
isLoading={isCasesStatusLoading}
/>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<StatusStats
dataTestSubj="closedStatsHeader"
caseCount={countClosedCases}
caseStatus={CaseStatuses.closed}
isLoading={isCasesStatusLoading}
/>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<EuiDescriptionList
data-test-subj={'mttrStatsHeader'}
textStyle="reverse"
listItems={[
{
title: (
<EuiToolTip position="right" content={ATTC_DESCRIPTION}>
<>
{ATTC_STAT} <EuiIcon type="questionInCircle" />
</>
</EuiToolTip>
),
description: isCasesMetricsLoading ? (
<EuiLoadingSpinner data-test-subj={`mttr-stat-loading-spinner`} />
) : (
mttrValue
),
},
]}
/>
</EuiFlexItem>
</EuiFlexGroup>
</MetricsFlexGroup>
);
};
CasesMetrics.displayName = 'CasesMetrics';
59 changes: 0 additions & 59 deletions x-pack/plugins/cases/public/components/all_cases/count.tsx

This file was deleted.

Loading

0 comments on commit 503b469

Please sign in to comment.