mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* [APM] Distinguish between loading state and empty state * Remove async describe * Fix tests * Show empty state outside table for agent configuration list * Fix translations
This commit is contained in:
parent
f44890030a
commit
96c5901fe0
11 changed files with 134 additions and 71 deletions
|
@ -9,13 +9,19 @@ import { i18n } from '@kbn/i18n';
|
|||
import React from 'react';
|
||||
import { KibanaLink } from '../../shared/Links/KibanaLink';
|
||||
import { SetupInstructionsLink } from '../../shared/Links/SetupInstructionsLink';
|
||||
import { LoadingStatePrompt } from '../../shared/LoadingStatePrompt';
|
||||
|
||||
interface Props {
|
||||
// any data submitted from APM agents found (not just in the given time range)
|
||||
historicalDataFound: boolean;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export function NoServicesMessage({ historicalDataFound }: Props) {
|
||||
export function NoServicesMessage({ historicalDataFound, isLoading }: Props) {
|
||||
if (isLoading) {
|
||||
return <LoadingStatePrompt />;
|
||||
}
|
||||
|
||||
if (historicalDataFound) {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
|
@ -29,48 +35,42 @@ export function NoServicesMessage({ historicalDataFound }: Props) {
|
|||
titleSize="s"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
<div>
|
||||
{i18n.translate('xpack.apm.servicesTable.noServicesLabel', {
|
||||
defaultMessage: `Looks like you don't have any APM services installed. Let's add some!`
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
titleSize="s"
|
||||
body={
|
||||
<React.Fragment>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.apm.servicesTable.7xUpgradeServerMessage',
|
||||
{
|
||||
defaultMessage: `Upgrading from a pre-7.x version? Make sure you've also upgraded
|
||||
your APM server instance(s) to at least 7.0.`
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{i18n.translate('xpack.apm.servicesTable.7xOldDataMessage', {
|
||||
defaultMessage:
|
||||
'You may also have old data that needs to be migrated.'
|
||||
})}{' '}
|
||||
<KibanaLink path="/management/elasticsearch/upgrade_assistant">
|
||||
{i18n.translate(
|
||||
'xpack.apm.servicesTable.UpgradeAssistantLink',
|
||||
{
|
||||
defaultMessage:
|
||||
'Learn more by visiting the Kibana Upgrade Assistant'
|
||||
}
|
||||
)}
|
||||
</KibanaLink>
|
||||
.
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
actions={<SetupInstructionsLink buttonFill={true} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
<div>
|
||||
{i18n.translate('xpack.apm.servicesTable.noServicesLabel', {
|
||||
defaultMessage: `Looks like you don't have any APM services installed. Let's add some!`
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
titleSize="s"
|
||||
body={
|
||||
<React.Fragment>
|
||||
<p>
|
||||
{i18n.translate('xpack.apm.servicesTable.7xUpgradeServerMessage', {
|
||||
defaultMessage: `Upgrading from a pre-7.x version? Make sure you've also upgraded
|
||||
your APM server instance(s) to at least 7.0.`
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{i18n.translate('xpack.apm.servicesTable.7xOldDataMessage', {
|
||||
defaultMessage:
|
||||
'You may also have old data that needs to be migrated.'
|
||||
})}{' '}
|
||||
<KibanaLink path="/management/elasticsearch/upgrade_assistant">
|
||||
{i18n.translate('xpack.apm.servicesTable.UpgradeAssistantLink', {
|
||||
defaultMessage:
|
||||
'Learn more by visiting the Kibana Upgrade Assistant'
|
||||
})}
|
||||
</KibanaLink>
|
||||
.
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
actions={<SetupInstructionsLink buttonFill={true} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,12 +10,16 @@ import { NoServicesMessage } from '../NoServicesMessage';
|
|||
|
||||
describe('NoServicesMessage', () => {
|
||||
it('should show only a "not found" message when historical data is found', () => {
|
||||
const wrapper = shallow(<NoServicesMessage historicalDataFound={true} />);
|
||||
const wrapper = shallow(
|
||||
<NoServicesMessage isLoading={false} historicalDataFound={true} />
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should show a "no services installed" message, a link to the set up instructions page, a message about upgrading APM server, and a link to the upgrade assistant when NO historical data is found', () => {
|
||||
const wrapper = shallow(<NoServicesMessage historicalDataFound={false} />);
|
||||
const wrapper = shallow(
|
||||
<NoServicesMessage isLoading={false} historicalDataFound={false} />
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,7 +30,7 @@ export function ServiceOverview() {
|
|||
urlParams: { start, end },
|
||||
uiFilters
|
||||
} = useUrlParams();
|
||||
const { data = initalData } = useFetcher(() => {
|
||||
const { data = initalData, status } = useFetcher(() => {
|
||||
if (start && end) {
|
||||
return loadServiceList({ start, end, uiFilters });
|
||||
}
|
||||
|
@ -75,7 +75,10 @@ export function ServiceOverview() {
|
|||
<ServiceList
|
||||
items={data.items}
|
||||
noItemsMessage={
|
||||
<NoServicesMessage historicalDataFound={data.hasHistoricalData} />
|
||||
<NoServicesMessage
|
||||
historicalDataFound={data.hasHistoricalData}
|
||||
isLoading={status === 'loading'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
|
|
|
@ -28,11 +28,15 @@ import { ITableColumn, ManagedTable } from '../../shared/ManagedTable';
|
|||
import { AgentConfigurationListAPIResponse } from '../../../../server/lib/settings/agent_configuration/list_configurations';
|
||||
import { AddSettingsFlyout } from './AddSettings/AddSettingFlyout';
|
||||
import { APMLink } from '../../shared/Links/APMLink';
|
||||
import { LoadingStatePrompt } from '../../shared/LoadingStatePrompt';
|
||||
|
||||
export type Config = AgentConfigurationListAPIResponse[0];
|
||||
|
||||
export function SettingsList() {
|
||||
const { data = [], refresh } = useFetcher(loadAgentConfigurationList, []);
|
||||
const { data = [], status, refresh } = useFetcher(
|
||||
loadAgentConfigurationList,
|
||||
[]
|
||||
);
|
||||
const [selectedConfig, setSelectedConfig] = useState<Config | null>(null);
|
||||
const [isFlyoutOpen, setIsFlyoutOpen] = useState(false);
|
||||
|
||||
|
@ -129,7 +133,7 @@ export function SettingsList() {
|
|||
|
||||
const hasConfigurations = !isEmpty(data);
|
||||
|
||||
const emptyState = (
|
||||
const emptyStatePrompt = (
|
||||
<EuiEmptyPrompt
|
||||
iconType="controlsHorizontal"
|
||||
title={
|
||||
|
@ -288,16 +292,17 @@ export function SettingsList() {
|
|||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
{hasConfigurations ? (
|
||||
{status === 'success' && !hasConfigurations ? (
|
||||
emptyStatePrompt
|
||||
) : (
|
||||
<ManagedTable
|
||||
noItemsMessage={<LoadingStatePrompt />}
|
||||
columns={COLUMNS}
|
||||
items={data}
|
||||
initialSortField="service.name"
|
||||
initialSortDirection="asc"
|
||||
initialPageSize={50}
|
||||
/>
|
||||
) : (
|
||||
emptyState
|
||||
)}
|
||||
</EuiPanel>
|
||||
</>
|
||||
|
|
|
@ -15,6 +15,7 @@ import { EmptyMessage } from '../../shared/EmptyMessage';
|
|||
import { ImpactBar } from '../../shared/ImpactBar';
|
||||
import { TransactionLink } from '../../shared/Links/TransactionLink';
|
||||
import { ITableColumn, ManagedTable } from '../../shared/ManagedTable';
|
||||
import { LoadingStatePrompt } from '../../shared/LoadingStatePrompt';
|
||||
|
||||
const StyledTransactionLink = styled(TransactionLink)`
|
||||
font-size: ${fontSizes.large};
|
||||
|
@ -97,7 +98,7 @@ const noItemsMessage = (
|
|||
);
|
||||
|
||||
export function TraceList({ items = [], isLoading }: Props) {
|
||||
const noItems = isLoading ? null : noItemsMessage;
|
||||
const noItems = isLoading ? <LoadingStatePrompt /> : noItemsMessage;
|
||||
return (
|
||||
<ManagedTable
|
||||
columns={traceListColumns}
|
||||
|
|
|
@ -18,6 +18,7 @@ import Histogram from '../../../shared/charts/Histogram';
|
|||
import { EmptyMessage } from '../../../shared/EmptyMessage';
|
||||
import { fromQuery, toQuery } from '../../../shared/Links/url_helpers';
|
||||
import { history } from '../../../../utils/history';
|
||||
import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt';
|
||||
|
||||
interface IChartPoint {
|
||||
sample?: IBucket['sample'];
|
||||
|
@ -90,7 +91,7 @@ const getFormatYLong = (transactionType: string | undefined) => (t: number) => {
|
|||
interface Props {
|
||||
distribution?: ITransactionDistributionAPIResponse;
|
||||
urlParams: IUrlParams;
|
||||
loading: boolean;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export const TransactionDistribution: FunctionComponent<Props> = (
|
||||
|
@ -99,7 +100,7 @@ export const TransactionDistribution: FunctionComponent<Props> = (
|
|||
const {
|
||||
distribution,
|
||||
urlParams: { transactionId, traceId, transactionType },
|
||||
loading
|
||||
isLoading
|
||||
} = props;
|
||||
|
||||
const formatYShort = useCallback(getFormatYShort(transactionType), [
|
||||
|
@ -125,10 +126,10 @@ export const TransactionDistribution: FunctionComponent<Props> = (
|
|||
...defaultSample
|
||||
})
|
||||
});
|
||||
}, [distribution, loading]);
|
||||
}, [distribution, isLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
const selectedSampleIsAvailable = distribution
|
||||
|
@ -145,7 +146,17 @@ export const TransactionDistribution: FunctionComponent<Props> = (
|
|||
if (!selectedSampleIsAvailable && !!distribution) {
|
||||
redirectToDefaultSample();
|
||||
}
|
||||
}, [distribution, transactionId, traceId, redirectToDefaultSample, loading]);
|
||||
}, [
|
||||
distribution,
|
||||
transactionId,
|
||||
traceId,
|
||||
redirectToDefaultSample,
|
||||
isLoading
|
||||
]);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingStatePrompt />;
|
||||
}
|
||||
|
||||
if (!distribution || !distribution.totalHits || !traceId || !transactionId) {
|
||||
return (
|
||||
|
|
|
@ -61,7 +61,7 @@ export function TransactionDetails() {
|
|||
<EuiPanel>
|
||||
<TransactionDistribution
|
||||
distribution={distributionData}
|
||||
loading={
|
||||
isLoading={
|
||||
distributionStatus === FETCH_STATUS.LOADING ||
|
||||
distributionStatus === undefined
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ import { ImpactBar } from '../../../shared/ImpactBar';
|
|||
import { APMLink } from '../../../shared/Links/APMLink';
|
||||
import { legacyEncodeURIComponent } from '../../../shared/Links/url_helpers';
|
||||
import { ITableColumn, ManagedTable } from '../../../shared/ManagedTable';
|
||||
import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt';
|
||||
import { EmptyMessage } from '../../../shared/EmptyMessage';
|
||||
|
||||
const TransactionNameLink = styled(APMLink)`
|
||||
${truncate('100%')};
|
||||
|
@ -25,9 +27,10 @@ const TransactionNameLink = styled(APMLink)`
|
|||
interface Props {
|
||||
items: ITransactionGroup[];
|
||||
serviceName: string;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export function TransactionList({ items, serviceName, ...rest }: Props) {
|
||||
export function TransactionList({ items, serviceName, isLoading }: Props) {
|
||||
const columns: Array<ITableColumn<ITransactionGroup>> = useMemo(
|
||||
() => [
|
||||
{
|
||||
|
@ -111,14 +114,22 @@ export function TransactionList({ items, serviceName, ...rest }: Props) {
|
|||
[serviceName]
|
||||
);
|
||||
|
||||
const noItemsMessage = (
|
||||
<EmptyMessage
|
||||
heading={i18n.translate('xpack.apm.transactionsTable.notFoundLabel', {
|
||||
defaultMessage: 'No transactions were found.'
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<ManagedTable
|
||||
noItemsMessage={isLoading ? <LoadingStatePrompt /> : noItemsMessage}
|
||||
columns={columns}
|
||||
items={items}
|
||||
initialSortField="impact"
|
||||
initialSortDirection="desc"
|
||||
initialPageSize={25}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -78,7 +78,10 @@ export function TransactionOverview({
|
|||
return null;
|
||||
}
|
||||
|
||||
const { data: transactionListData } = useTransactionList(urlParams);
|
||||
const {
|
||||
data: transactionListData,
|
||||
status: transactionListStatus
|
||||
} = useTransactionList(urlParams);
|
||||
const { data: hasMLJob = false } = useFetcher(
|
||||
() => getHasMLJob({ serviceName, transactionType }),
|
||||
[serviceName, transactionType]
|
||||
|
@ -134,6 +137,7 @@ export function TransactionOverview({
|
|||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<TransactionList
|
||||
isLoading={transactionListStatus === 'loading'}
|
||||
items={transactionListData}
|
||||
serviceName={serviceName}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiEmptyPrompt } from '@elastic/eui';
|
||||
|
||||
export function LoadingStatePrompt() {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
<div>
|
||||
{i18n.translate('xpack.apm.loading.prompt', {
|
||||
defaultMessage: 'Loading...'
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
titleSize="s"
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -13,7 +13,7 @@ import { initTracesApi } from '../traces';
|
|||
describe('route handlers should fail with a Boom error', () => {
|
||||
let consoleErrorSpy: any;
|
||||
|
||||
async function testRouteFailures(init: (core: InternalCoreSetup) => void) {
|
||||
function testRouteFailures(init: (core: InternalCoreSetup) => void) {
|
||||
const mockServer = { route: jest.fn() };
|
||||
const mockCore = ({
|
||||
http: {
|
||||
|
@ -44,7 +44,7 @@ describe('route handlers should fail with a Boom error', () => {
|
|||
};
|
||||
|
||||
const routes = flatten(mockServer.route.mock.calls);
|
||||
routes.forEach(async (route, i) => {
|
||||
routes.forEach((route, i) => {
|
||||
test(`${route.method} ${route.path}"`, async () => {
|
||||
await expect(route.handler(mockReq)).rejects.toMatchObject({
|
||||
message: 'request failed',
|
||||
|
@ -65,15 +65,15 @@ describe('route handlers should fail with a Boom error', () => {
|
|||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
describe('error routes', async () => {
|
||||
await testRouteFailures(initErrorsApi);
|
||||
describe('error routes', () => {
|
||||
testRouteFailures(initErrorsApi);
|
||||
});
|
||||
|
||||
describe('service routes', async () => {
|
||||
await testRouteFailures(initServicesApi);
|
||||
describe('service routes', () => {
|
||||
testRouteFailures(initServicesApi);
|
||||
});
|
||||
|
||||
describe('trace routes', async () => {
|
||||
await testRouteFailures(initTracesApi);
|
||||
describe('trace routes', () => {
|
||||
testRouteFailures(initTracesApi);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue