mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Logs onboarding] Error messages for progress steps (#161599)
This commit is contained in:
parent
f9adf3bbf7
commit
0d3629f9f7
9 changed files with 208 additions and 82 deletions
|
@ -53,12 +53,13 @@ artifact=elastic-agent-${ELASTIC_AGENT_VERSION}-${os}-${arch}
|
|||
updateStepProgress() {
|
||||
local STEPNAME="$1"
|
||||
local STATUS="$2" # "incomplete" | "complete" | "disabled" | "loading" | "warning" | "danger" | "current"
|
||||
local MESSAGE=${3:-}
|
||||
curl --request POST \
|
||||
--url "${API_ENDPOINT}/custom_logs/${ONBOARDING_ID}/step/${STEPNAME}" \
|
||||
--header "Authorization: ApiKey ${API_KEY_ENCODED}" \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "kbn-xsrf: true" \
|
||||
--data "{\"status\":\"${STATUS}\"}" \
|
||||
--data "{\"status\":\"${STATUS}\", \"message\":\"${MESSAGE}\"}" \
|
||||
--output /dev/null \
|
||||
--no-progress-meter
|
||||
}
|
||||
|
@ -71,19 +72,18 @@ if [ "$?" -eq 0 ]; then
|
|||
echo "Downloaded Elastic Agent"
|
||||
updateStepProgress "ea-download" "complete"
|
||||
else
|
||||
updateStepProgress "ea-download" "danger"
|
||||
updateStepProgress "ea-download" "danger" "Failed to download Elastic Agent, see script output for error."
|
||||
fail "Failed to download Elastic Agent"
|
||||
fi
|
||||
|
||||
echo "Extracting Elastic Agent"
|
||||
updateStepProgress "ea-extract" "loading"
|
||||
tar -xzf ${artifact}.tar.gz
|
||||
echo ""
|
||||
if [ "$?" -eq 0 ]; then
|
||||
echo "Elastic Agent extracted"
|
||||
updateStepProgress "ea-extract" "complete"
|
||||
else
|
||||
updateStepProgress "ea-extract" "danger"
|
||||
updateStepProgress "ea-extract" "danger" "Failed to extract Elastic Agent, see script output for error."
|
||||
fail "Failed to extract Elastic Agent"
|
||||
fi
|
||||
|
||||
|
@ -95,7 +95,7 @@ if [ "$?" -eq 0 ]; then
|
|||
echo "Elastic Agent installed"
|
||||
updateStepProgress "ea-install" "complete"
|
||||
else
|
||||
updateStepProgress "ea-install" "danger"
|
||||
updateStepProgress "ea-install" "danger" "Failed to install Elastic Agent, see script output for error."
|
||||
fail "Failed to install Elastic Agent"
|
||||
fi
|
||||
|
||||
|
@ -120,8 +120,7 @@ echo "Checking Elastic Agent status"
|
|||
updateStepProgress "ea-status" "loading"
|
||||
waitForElasticAgentStatus
|
||||
if [ "$?" -ne 0 ]; then
|
||||
updateStepProgress "ea-status" "warning"
|
||||
exit 1
|
||||
updateStepProgress "ea-status" "warning" "Unable to determine agent status"
|
||||
fi
|
||||
ELASTIC_AGENT_STATE="$(elastic-agent status | grep -m1 State | sed 's/State: //')"
|
||||
ELASTIC_AGENT_MESSAGE="$(elastic-agent status | grep -m1 Message | sed 's/Message: //')"
|
||||
|
@ -130,8 +129,7 @@ if [ "${ELASTIC_AGENT_STATE}" = "HEALTHY" ] && [ "${ELASTIC_AGENT_MESSAGE}" = "R
|
|||
echo "Download and save configuration to /opt/Elastic/Agent/elastic-agent.yml"
|
||||
updateStepProgress "ea-status" "complete"
|
||||
else
|
||||
updateStepProgress "ea-status" "warning"
|
||||
exit 1
|
||||
updateStepProgress "ea-status" "warning" "Expected agent status HEALTHY / Running but got ${ELASTIC_AGENT_STATE} / ${ELASTIC_AGENT_MESSAGE}"
|
||||
fi
|
||||
|
||||
downloadElasticAgentConfig() {
|
||||
|
@ -149,12 +147,12 @@ downloadElasticAgentConfig() {
|
|||
echo "Downloaded elastic-agent.yml"
|
||||
updateStepProgress "ea-config" "complete"
|
||||
else
|
||||
updateStepProgress "ea-config" "warning"
|
||||
updateStepProgress "ea-config" "warning" "Failed to write elastic-agent.yml on host automatically, try manually setting the configuration"
|
||||
fail "Failed to download elastic-agent.yml"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "${AUTO_DOWNLOAD_CONFIG}" == *"autoDownloadConfig=1"* ]; then
|
||||
if [ "${AUTO_DOWNLOAD_CONFIG}" == "autoDownloadConfig=1" ]; then
|
||||
downloadElasticAgentConfig
|
||||
echo "Done with standalone Elastic Agent setup for custom logs. Look for streaming logs to arrive in Kibana"
|
||||
else
|
||||
|
|
|
@ -14,14 +14,11 @@ import {
|
|||
EuiFlexItem,
|
||||
EuiSkeletonRectangle,
|
||||
EuiSpacer,
|
||||
EuiStep,
|
||||
EuiSteps,
|
||||
EuiStepsProps,
|
||||
EuiSubSteps,
|
||||
EuiSwitch,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Buffer } from 'buffer';
|
||||
import { flatten, zip } from 'lodash';
|
||||
|
@ -36,6 +33,7 @@ import {
|
|||
} from '../../../shared/step_panel';
|
||||
import { ApiKeyBanner } from './api_key_banner';
|
||||
import { BackButton } from './back_button';
|
||||
import { StepStatus } from './step_status';
|
||||
|
||||
type ElasticAgentPlatform = 'linux-tar' | 'macos' | 'windows';
|
||||
export function InstallElasticAgent() {
|
||||
|
@ -198,9 +196,8 @@ export function InstallElasticAgent() {
|
|||
({ id, incompleteTitle, loadingTitle, completedTitle }) => {
|
||||
const progress = progressData?.progress;
|
||||
if (progress) {
|
||||
const stepStatus = progress[
|
||||
id
|
||||
] as EuiStepsProps['steps'][number]['status'];
|
||||
const stepStatus = progress?.[id]
|
||||
?.status as EuiStepsProps['steps'][number]['status'];
|
||||
const title =
|
||||
stepStatus === 'loading'
|
||||
? loadingTitle
|
||||
|
@ -211,6 +208,7 @@ export function InstallElasticAgent() {
|
|||
title,
|
||||
children: null,
|
||||
status: stepStatus ?? ('incomplete' as const),
|
||||
message: progress?.[id]?.message,
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
@ -223,7 +221,8 @@ export function InstallElasticAgent() {
|
|||
);
|
||||
|
||||
const isInstallStarted = progressData?.progress['ea-download'] !== undefined;
|
||||
const isInstallCompleted = progressData?.progress['ea-status'] === 'complete';
|
||||
const isInstallCompleted =
|
||||
progressData?.progress?.['ea-status']?.status === 'complete';
|
||||
|
||||
const autoDownloadConfigStep = getStep({
|
||||
id: 'ea-config',
|
||||
|
@ -404,7 +403,7 @@ export function InstallElasticAgent() {
|
|||
{isInstallStarted && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSubSteps>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{[
|
||||
{
|
||||
id: 'ea-download',
|
||||
|
@ -473,36 +472,26 @@ export function InstallElasticAgent() {
|
|||
),
|
||||
},
|
||||
].map((step, index) => {
|
||||
const { title, status } = getStep(step);
|
||||
const { title, status, message } = getStep(step);
|
||||
return (
|
||||
<EuiStep
|
||||
key={step.id}
|
||||
titleSize="xs"
|
||||
step={index + 1}
|
||||
title={title}
|
||||
<StepStatus
|
||||
status={status}
|
||||
children={null}
|
||||
css={css({
|
||||
'> .euiStep__content': {
|
||||
paddingBottom: 0,
|
||||
},
|
||||
})}
|
||||
title={title}
|
||||
message={message}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</EuiSubSteps>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: wizardState.autoDownloadConfig
|
||||
? autoDownloadConfigStep.title
|
||||
: i18n.translate(
|
||||
'xpack.observability_onboarding.installElasticAgent.progress.eaConfig.incompleteTitle',
|
||||
{ defaultMessage: 'Configure the agent' }
|
||||
),
|
||||
title: i18n.translate(
|
||||
'xpack.observability_onboarding.installElasticAgent.configureStep.title',
|
||||
{ defaultMessage: 'Configure the Elastic agent' }
|
||||
),
|
||||
status:
|
||||
yamlConfigStatus === FETCH_STATUS.LOADING
|
||||
? 'loading'
|
||||
|
@ -574,6 +563,14 @@ export function InstallElasticAgent() {
|
|||
{ defaultMessage: 'Download config file' }
|
||||
)}
|
||||
</EuiButton>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup direction="column">
|
||||
<StepStatus
|
||||
status={autoDownloadConfigStep.status}
|
||||
title={autoDownloadConfigStep.title}
|
||||
message={autoDownloadConfigStep.message}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
),
|
||||
},
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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,
|
||||
EuiStepsProps,
|
||||
EuiPanel,
|
||||
EuiText,
|
||||
EuiCallOut,
|
||||
EuiLoadingSpinner,
|
||||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
export function StepStatus({
|
||||
status,
|
||||
title,
|
||||
message,
|
||||
}: {
|
||||
status: EuiStepsProps['steps'][number]['status'];
|
||||
title: string;
|
||||
message?: string;
|
||||
}) {
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiPanel color="transparent">
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="m" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued">{title}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
if (status === 'complete') {
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut title={title} color="success" iconType="check">
|
||||
{message}
|
||||
</EuiCallOut>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
if (status === 'danger') {
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut title={title} color="danger" iconType="warning">
|
||||
{message}
|
||||
</EuiCallOut>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
if (status === 'warning') {
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut title={title} color="warning" iconType="warning">
|
||||
{message}
|
||||
</EuiCallOut>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -97,7 +97,10 @@ const createApiKeyRoute = createObservabilityOnboardingServerRoute({
|
|||
|
||||
const { id } = await saveObservabilityOnboardingState({
|
||||
savedObjectsClient,
|
||||
observabilityOnboardingState: { state } as ObservabilityOnboardingState,
|
||||
observabilityOnboardingState: {
|
||||
state: state as ObservabilityOnboardingState['state'],
|
||||
progress: {},
|
||||
},
|
||||
});
|
||||
|
||||
return { apiKeyEncoded, onboardingId: id };
|
||||
|
@ -145,15 +148,18 @@ const stepProgressUpdateRoute = createObservabilityOnboardingServerRoute({
|
|||
id: t.string,
|
||||
name: t.string,
|
||||
}),
|
||||
body: t.type({
|
||||
status: t.string,
|
||||
}),
|
||||
body: t.intersection([
|
||||
t.type({
|
||||
status: t.string,
|
||||
}),
|
||||
t.partial({ message: t.string }),
|
||||
]),
|
||||
}),
|
||||
async handler(resources): Promise<object> {
|
||||
async handler(resources) {
|
||||
const {
|
||||
params: {
|
||||
path: { id, name },
|
||||
body: { status },
|
||||
body: { status, message },
|
||||
},
|
||||
core,
|
||||
} = resources;
|
||||
|
@ -186,11 +192,11 @@ const stepProgressUpdateRoute = createObservabilityOnboardingServerRoute({
|
|||
...observabilityOnboardingState,
|
||||
progress: {
|
||||
...observabilityOnboardingState.progress,
|
||||
[name]: status,
|
||||
[name]: { status, message },
|
||||
},
|
||||
},
|
||||
});
|
||||
return { name, status };
|
||||
return { name, status, message };
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -203,7 +209,9 @@ const getProgressRoute = createObservabilityOnboardingServerRoute({
|
|||
onboardingId: t.string,
|
||||
}),
|
||||
}),
|
||||
async handler(resources): Promise<{ progress: Record<string, string> }> {
|
||||
async handler(resources): Promise<{
|
||||
progress: Record<string, { status: string; message?: string }>;
|
||||
}> {
|
||||
const {
|
||||
params: {
|
||||
path: { onboardingId },
|
||||
|
@ -233,7 +241,7 @@ const getProgressRoute = createObservabilityOnboardingServerRoute({
|
|||
const {
|
||||
state: { datasetName: dataset, namespace },
|
||||
} = savedObservabilityOnboardingState;
|
||||
if (progress['ea-status'] === 'complete') {
|
||||
if (progress['ea-status']?.status === 'complete') {
|
||||
try {
|
||||
const hasLogs = await getHasLogs({
|
||||
dataset,
|
||||
|
@ -241,15 +249,15 @@ const getProgressRoute = createObservabilityOnboardingServerRoute({
|
|||
esClient,
|
||||
});
|
||||
if (hasLogs) {
|
||||
progress['logs-ingest'] = 'complete';
|
||||
progress['logs-ingest'] = { status: 'complete' };
|
||||
} else {
|
||||
progress['logs-ingest'] = 'loading';
|
||||
progress['logs-ingest'] = { status: 'loading' };
|
||||
}
|
||||
} catch (error) {
|
||||
progress['logs-ingest'] = 'warning';
|
||||
progress['logs-ingest'] = { status: 'warning', message: error.message };
|
||||
}
|
||||
} else {
|
||||
progress['logs-ingest'] = 'incomplete';
|
||||
progress['logs-ingest'] = { status: 'incomplete' };
|
||||
}
|
||||
|
||||
return { progress };
|
||||
|
|
|
@ -30,7 +30,10 @@ export async function saveObservabilityOnboardingState({
|
|||
savedObject = await savedObjectsClient.update<ObservabilityOnboardingState>(
|
||||
OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE,
|
||||
savedObjectId,
|
||||
observabilityOnboardingState
|
||||
{
|
||||
state: observabilityOnboardingState.state,
|
||||
progress: { ...observabilityOnboardingState.progress },
|
||||
}
|
||||
);
|
||||
} else {
|
||||
savedObject = await savedObjectsClient.create<ObservabilityOnboardingState>(
|
||||
|
|
|
@ -18,7 +18,13 @@ export interface ObservabilityOnboardingState {
|
|||
logFilePaths: string[];
|
||||
namespace: string;
|
||||
};
|
||||
progress: Record<string, string>;
|
||||
progress: Record<
|
||||
string,
|
||||
{
|
||||
status: string;
|
||||
message?: string;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
export interface SavedObservabilityOnboardingState
|
||||
|
|
|
@ -95,7 +95,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
expect(request.status).to.be(200);
|
||||
|
||||
expect(request.body.progress['logs-ingest']).to.be('incomplete');
|
||||
const logsIngestProgress = request.body.progress['logs-ingest'];
|
||||
expect(logsIngestProgress).to.have.property('status', 'incomplete');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -124,7 +125,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
expect(request.status).to.be(200);
|
||||
|
||||
expect(request.body.progress['logs-ingest']).to.be('loading');
|
||||
const logsIngestProgress = request.body.progress['logs-ingest'];
|
||||
expect(logsIngestProgress).to.have.property('status', 'loading');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -156,7 +158,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
expect(request.status).to.be(200);
|
||||
|
||||
expect(request.body.progress['logs-ingest']).to.be('complete');
|
||||
const logsIngestProgress = request.body.progress['logs-ingest'];
|
||||
expect(logsIngestProgress).to.have.property('status', 'complete');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
|
|
|
@ -76,7 +76,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
id: request.body.onboardingId,
|
||||
});
|
||||
|
||||
expect(savedState.attributes).to.be.eql({ state });
|
||||
expect(savedState.attributes).to.be.eql({ state, progress: {} });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,7 +16,17 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
const kibanaServer = getService('kibanaServer');
|
||||
const observabilityOnboardingApiClient = getService('observabilityOnboardingApiClient');
|
||||
|
||||
async function callApi({ id, name, status }: { id: string; name: string; status: string }) {
|
||||
async function callApi({
|
||||
id,
|
||||
name,
|
||||
status,
|
||||
message,
|
||||
}: {
|
||||
id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
message?: string;
|
||||
}) {
|
||||
return await observabilityOnboardingApiClient.logMonitoringUser({
|
||||
endpoint: 'POST /internal/observability_onboarding/custom_logs/{id}/step/{name}',
|
||||
params: {
|
||||
|
@ -26,28 +36,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
},
|
||||
body: {
|
||||
status,
|
||||
message,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
registry.when('Update step progress', { config: 'basic' }, () => {
|
||||
let onboardingId: string;
|
||||
|
||||
before(async () => {
|
||||
const req = await observabilityOnboardingApiClient.logMonitoringUser({
|
||||
endpoint: 'POST /internal/observability_onboarding/custom_logs/save',
|
||||
params: {
|
||||
body: {
|
||||
name: 'name',
|
||||
state: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
onboardingId = req.body.onboardingId;
|
||||
});
|
||||
|
||||
describe("when onboardingId doesn't exists", () => {
|
||||
it('fails with a 404 error', async () => {
|
||||
const err = await expectToReject<ObservabilityOnboardingApiError>(
|
||||
|
@ -65,21 +60,34 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('when onboardingId exists', () => {
|
||||
const step = {
|
||||
name: 'ea-download',
|
||||
status: 'complete',
|
||||
};
|
||||
let onboardingId: string;
|
||||
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
const req = await observabilityOnboardingApiClient.logMonitoringUser({
|
||||
endpoint: 'POST /internal/observability_onboarding/custom_logs/save',
|
||||
params: {
|
||||
body: {
|
||||
name: 'name',
|
||||
state: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
onboardingId = req.body.onboardingId;
|
||||
const savedState = await kibanaServer.savedObjects.get({
|
||||
type: OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE,
|
||||
id: onboardingId,
|
||||
});
|
||||
|
||||
expect(savedState.attributes.progress?.[step.name]).not.ok();
|
||||
expect(savedState.attributes.progress).eql({});
|
||||
});
|
||||
|
||||
it('updates step status', async () => {
|
||||
const step = {
|
||||
name: 'ea-download',
|
||||
status: 'complete',
|
||||
};
|
||||
|
||||
const request = await callApi({
|
||||
id: onboardingId,
|
||||
...step,
|
||||
|
@ -92,7 +100,38 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
id: onboardingId,
|
||||
});
|
||||
|
||||
expect(savedState.attributes.progress?.[step.name]).to.be(step.status);
|
||||
const stepProgress = savedState.attributes.progress?.[step.name];
|
||||
expect(stepProgress).to.have.property('status', step.status);
|
||||
});
|
||||
|
||||
it('updates step status with message', async () => {
|
||||
const step = {
|
||||
name: 'ea-download',
|
||||
status: 'danger',
|
||||
message: 'Download failed',
|
||||
};
|
||||
const request = await callApi({
|
||||
id: onboardingId,
|
||||
...step,
|
||||
});
|
||||
|
||||
expect(request.status).to.be(200);
|
||||
|
||||
const savedState = await kibanaServer.savedObjects.get({
|
||||
type: OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE,
|
||||
id: onboardingId,
|
||||
});
|
||||
|
||||
const stepProgress = savedState.attributes.progress?.[step.name];
|
||||
expect(stepProgress).to.have.property('status', step.status);
|
||||
expect(stepProgress).to.have.property('message', step.message);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await kibanaServer.savedObjects.delete({
|
||||
type: OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE,
|
||||
id: onboardingId,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue