Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[APM] Link to Fleet APM Server Configuration when managed by Elastic Agent w/Fleet #100816

Merged
merged 37 commits into from
Jun 24, 2021
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
515c1c9
Register tutorial on APM plugin
cauemarcondes May 26, 2021
25fec3f
using files from apm
cauemarcondes May 26, 2021
3afb580
removing tutorial from apm_oss
cauemarcondes May 26, 2021
7195da0
removing export
cauemarcondes May 27, 2021
e38e390
fixing i18n
cauemarcondes May 27, 2021
92d34b1
Merge branch 'master' into apm-moving-tutorial
kibanamachine May 27, 2021
3f484a7
adding fleet section
cauemarcondes May 27, 2021
07f91b5
adding fleet information on APM tutorial
cauemarcondes May 31, 2021
23695bc
Merge branch 'master' of github.com:elastic/kibana into apm-fleet-tut…
cauemarcondes May 31, 2021
e854f34
adding fleet typing
cauemarcondes May 31, 2021
2a00cc7
fixing i18n
cauemarcondes May 31, 2021
0f0f458
adding fleet information on APM tutorial
cauemarcondes Jun 1, 2021
2f81e72
checks apm fleet integration when pushing button
cauemarcondes Jun 1, 2021
66b9351
adding fleet information on APM tutorial
cauemarcondes Jun 2, 2021
9990697
Merge branch 'master' into apm-fleet-tutorial
kibanamachine Jun 4, 2021
6b326e8
Merge branch 'master' into apm-fleet-tutorial
kibanamachine Jun 7, 2021
71a43d8
Merge branch 'master' of github.com:elastic/kibana into apm-fleet-tut…
cauemarcondes Jun 9, 2021
831e2c0
Merge branch 'apm-fleet-tutorial' of github.com:cauemarcondes/kibana …
cauemarcondes Jun 9, 2021
2c4112c
refactoring
cauemarcondes Jun 9, 2021
35c5672
registering status check callback
cauemarcondes Jun 10, 2021
7dadd85
addin custom component registration function
cauemarcondes Jun 10, 2021
ba03e53
fixing TS issue
cauemarcondes Jun 10, 2021
610aebb
Merge branch 'master' into apm-fleet-tutorial
kibanamachine Jun 14, 2021
1746467
addressing PR comments
cauemarcondes Jun 14, 2021
82e61eb
fixing tests
cauemarcondes Jun 14, 2021
c051953
adding i18n
cauemarcondes Jun 15, 2021
7e441d2
fixing issues
cauemarcondes Jun 15, 2021
f5c3464
Merge branch 'master' into apm-fleet-tutorial
kibanamachine Jun 16, 2021
5dddd48
adding unit test
cauemarcondes Jun 21, 2021
e9a4463
Merge branch 'master' of github.com:elastic/kibana into apm-fleet-tut…
cauemarcondes Jun 21, 2021
496f713
adding unit test
cauemarcondes Jun 21, 2021
2087a9d
addressing PR comments
cauemarcondes Jun 21, 2021
75e6e28
fixing TS issue
cauemarcondes Jun 22, 2021
98f89a1
Merge branch 'master' of github.com:elastic/kibana into apm-fleet-tut…
cauemarcondes Jun 22, 2021
157d7a4
moving tutorial to a common directory
cauemarcondes Jun 23, 2021
d7315f5
Merge branch 'master' into apm-fleet-tutorial
kibanamachine Jun 23, 2021
7795d73
Merge branch 'master' into apm-fleet-tutorial
kibanamachine Jun 24, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/plugins/home/common/instruction_variant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* Side Public License, v 1.
*/

import { i18n } from '@kbn/i18n';

export const INSTRUCTION_VARIANT = {
ESC: 'esc',
OSX: 'osx',
Expand All @@ -24,6 +26,7 @@ export const INSTRUCTION_VARIANT = {
DOTNET: 'dotnet',
LINUX: 'linux',
PHP: 'php',
FLEET: 'fleet',
};

const DISPLAY_MAP = {
Expand All @@ -44,6 +47,9 @@ const DISPLAY_MAP = {
[INSTRUCTION_VARIANT.DOTNET]: '.NET',
[INSTRUCTION_VARIANT.LINUX]: 'Linux',
[INSTRUCTION_VARIANT.PHP]: 'PHP',
[INSTRUCTION_VARIANT.FLEET]: i18n.translate('home.tutorial.instruction_variant.fleet', {
defaultMessage: 'Elastic APM (beta) in Fleet',
}),
};

/**
Expand Down
10 changes: 6 additions & 4 deletions src/plugins/home/public/application/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { i18n } from '@kbn/i18n';
import { ScopedHistory, CoreStart } from 'kibana/public';
import { KibanaContextProvider } from '../../../kibana_react/public';
import { KibanaContextProvider, RedirectAppLinks } from '../../../kibana_react/public';
// @ts-ignore
import { HomeApp } from './components/home_app';
import { getServices } from './kibana_services';
Expand Down Expand Up @@ -44,9 +44,11 @@ export const renderApp = async (
});

render(
<KibanaContextProvider services={{ ...coreStart }}>
<HomeApp directories={directories} solutions={solutions} />
</KibanaContextProvider>,
<RedirectAppLinks application={coreStart.application}>
<KibanaContextProvider services={{ ...coreStart }}>
<HomeApp directories={directories} solutions={solutions} />
</KibanaContextProvider>
</RedirectAppLinks>,
element
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import React from 'react';
import React, { Suspense, useMemo } from 'react';
import PropTypes from 'prop-types';
import { Content } from './content';

Expand All @@ -17,11 +17,23 @@ import {
EuiSpacer,
EuiCopy,
EuiButton,
EuiLoadingSpinner,
} from '@elastic/eui';

import { FormattedMessage } from '@kbn/i18n/react';

export function Instruction({ commands, paramValues, textPost, textPre, replaceTemplateStrings }) {
import { getServices } from '../../kibana_services';

export function Instruction({
commands,
paramValues,
textPost,
textPre,
replaceTemplateStrings,
customComponentName,
}) {
const { tutorialService, http, uiSettings, getBasePath } = getServices();

let pre;
if (textPre) {
pre = <Content text={replaceTemplateStrings(textPre)} />;
Expand All @@ -36,6 +48,13 @@ export function Instruction({ commands, paramValues, textPost, textPre, replaceT
</div>
);
}
const customComponent = tutorialService.getCustomComponent(customComponentName);
//Memoize the custom component so it wont rerender everytime
const LazyCustomComponent = useMemo(() => {
if (customComponent) {
return React.lazy(() => customComponent());
}
}, [customComponent]);

let copyButton;
let commandBlock;
Expand Down Expand Up @@ -79,6 +98,16 @@ export function Instruction({ commands, paramValues, textPost, textPre, replaceT

{post}

{LazyCustomComponent && (
<Suspense fallback={<EuiLoadingSpinner />}>
<LazyCustomComponent
basePath={getBasePath()}
isDarkTheme={uiSettings.get('theme:darkMode')}
http={http}
/>
</Suspense>
)}

<EuiSpacer />
</div>
);
Expand All @@ -90,4 +119,5 @@ Instruction.propTypes = {
textPost: PropTypes.string,
textPre: PropTypes.string,
replaceTemplateStrings: PropTypes.func.isRequired,
customComponentName: PropTypes.string,
};
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class InstructionSetUi extends React.Component {
textPre={instruction.textPre}
textPost={instruction.textPost}
replaceTemplateStrings={this.props.replaceTemplateStrings}
customComponentName={instruction.customComponentName}
/>
);
return {
Expand Down Expand Up @@ -298,6 +299,7 @@ const statusCheckConfigShape = PropTypes.shape({
title: PropTypes.string,
text: PropTypes.string,
btnLabel: PropTypes.string,
customStatusCheck: PropTypes.string,
});

InstructionSetUi.propTypes = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ class TutorialUi extends React.Component {

async componentDidMount() {
const tutorial = await this.props.getTutorial(this.props.tutorialId);

if (!this._isMounted) {
return;
}
Expand Down Expand Up @@ -172,15 +171,39 @@ class TutorialUi extends React.Component {
const instructionSet = this.getInstructionSets()[instructionSetIndex];
const esHitsCheckConfig = _.get(instructionSet, `statusCheck.esHitsCheck`);

if (esHitsCheckConfig) {
const statusCheckState = await this.fetchEsHitsStatus(esHitsCheckConfig);
//Checks if a custom status check callback was registered in the CLIENT
cauemarcondes marked this conversation as resolved.
Show resolved Hide resolved
//that matches the same name registered in the SERVER (customStatusCheckName)
const customStatusCheckCallback = getServices().tutorialService.getCustomStatusCheck(
this.state.tutorial.customStatusCheckName
);

this.setState((prevState) => ({
statusCheckStates: {
...prevState.statusCheckStates,
[instructionSetIndex]: statusCheckState,
},
}));
const [esHitsStatusCheck, customStatusCheck] = await Promise.all([
...(esHitsCheckConfig ? [this.fetchEsHitsStatus(esHitsCheckConfig)] : []),
...(customStatusCheckCallback
? [this.fetchCustomStatusCheck(customStatusCheckCallback)]
: []),
]);

const nextStatusCheckState =
esHitsStatusCheck === StatusCheckStates.HAS_DATA ||
customStatusCheck === StatusCheckStates.HAS_DATA
? StatusCheckStates.HAS_DATA
: StatusCheckStates.NO_DATA;

this.setState((prevState) => ({
statusCheckStates: {
...prevState.statusCheckStates,
[instructionSetIndex]: nextStatusCheckState,
},
}));
};

fetchCustomStatusCheck = async (customStatusCheckCallback) => {
try {
const response = await customStatusCheckCallback();
return response ? StatusCheckStates.HAS_DATA : StatusCheckStates.NO_DATA;
} catch (e) {
return StatusCheckStates.ERROR;
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,23 @@ import { Tutorial } from './tutorial';

jest.mock('../../kibana_services', () => ({
getServices: () => ({
http: {
post: jest.fn().mockImplementation(async () => ({ count: 1 })),
},
getBasePath: jest.fn(() => 'path'),
chrome: {
setBreadcrumbs: () => {},
},
tutorialService: {
getModuleNotices: () => [],
getCustomComponent: jest.fn(),
getCustomStatusCheck: (name) => {
const customStatusCheckMock = {
custom_status_check_has_data: async () => true,
custom_status_check_no_data: async () => false,
};
return customStatusCheckMock[name];
},
},
}),
}));
Expand Down Expand Up @@ -54,6 +65,7 @@ const tutorial = {
elasticCloud: buildInstructionSet('elasticCloud'),
onPrem: buildInstructionSet('onPrem'),
onPremElasticCloud: buildInstructionSet('onPremElasticCloud'),
customStatusCheckName: 'custom_status_check_has_data',
};
const loadTutorialPromise = Promise.resolve(tutorial);
const getTutorial = () => {
Expand Down Expand Up @@ -143,3 +155,104 @@ test('should render ELASTIC_CLOUD instructions when isCloudEnabled is true', asy
component.update();
expect(component).toMatchSnapshot(); // eslint-disable-line
});

describe('custom status check', () => {
test('should return has_data when custom status check callback is set and returns true', async () => {
const component = mountWithIntl(
<Tutorial.WrappedComponent
addBasePath={addBasePath}
isCloudEnabled={true}
getTutorial={getTutorial}
replaceTemplateStrings={replaceTemplateStrings}
tutorialId={'my_testing_tutorial'}
bulkCreate={() => {}}
/>
);
await loadTutorialPromise;
component.update();
await component.instance().checkInstructionSetStatus(0);
expect(component.state('statusCheckStates')[0]).toEqual('has_data');
});
test('should return no_data when custom status check callback is set and returns false', async () => {
const tutorialWithCustomStatusCheckNoData = {
...tutorial,
customStatusCheckName: 'custom_status_check_no_data',
};
const component = mountWithIntl(
<Tutorial.WrappedComponent
addBasePath={addBasePath}
isCloudEnabled={true}
getTutorial={async () => tutorialWithCustomStatusCheckNoData}
replaceTemplateStrings={replaceTemplateStrings}
tutorialId={'my_testing_tutorial'}
bulkCreate={() => {}}
/>
);
await loadTutorialPromise;
component.update();
await component.instance().checkInstructionSetStatus(0);
expect(component.state('statusCheckStates')[0]).toEqual('NO_DATA');
});

test('should return no_data when custom status check callback is not defined', async () => {
const tutorialWithoutCustomStatusCheck = {
...tutorial,
customStatusCheckName: undefined,
};
const component = mountWithIntl(
<Tutorial.WrappedComponent
addBasePath={addBasePath}
isCloudEnabled={true}
getTutorial={async () => tutorialWithoutCustomStatusCheck}
replaceTemplateStrings={replaceTemplateStrings}
tutorialId={'my_testing_tutorial'}
bulkCreate={() => {}}
/>
);
await loadTutorialPromise;
component.update();
await component.instance().checkInstructionSetStatus(0);
expect(component.state('statusCheckStates')[0]).toEqual('NO_DATA');
});

test('should return has_data if esHits or customStatusCheck returns true', async () => {
const { instructionSets } = tutorial.elasticCloud;
const tutorialWithStatusCheckAndCustomStatusCheck = {
...tutorial,
customStatusCheckName: undefined,
elasticCloud: {
instructionSets: [
{
...instructionSets[0],
statusCheck: {
title: 'check status',
text: 'check status',
esHitsCheck: {
index: 'foo',
query: {
bool: {
filter: [{ term: { 'processor.event': 'onboarding' } }],
},
},
},
},
},
],
},
};
const component = mountWithIntl(
<Tutorial.WrappedComponent
addBasePath={addBasePath}
isCloudEnabled={true}
getTutorial={async () => tutorialWithStatusCheckAndCustomStatusCheck}
replaceTemplateStrings={replaceTemplateStrings}
tutorialId={'my_testing_tutorial'}
bulkCreate={() => {}}
/>
);
await loadTutorialPromise;
component.update();
await component.instance().checkInstructionSetStatus(0);
expect(component.state('statusCheckStates')[0]).toEqual('has_data');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const createSetupMock = (): jest.Mocked<TutorialServiceSetup> => {
registerDirectoryNotice: jest.fn(),
registerDirectoryHeaderLink: jest.fn(),
registerModuleNotice: jest.fn(),
registerCustomStatusCheck: jest.fn(),
registerCustomComponent: jest.fn(),
};
return setup;
};
Expand All @@ -26,6 +28,8 @@ const createMock = (): jest.Mocked<PublicMethodsOf<TutorialService>> => {
getDirectoryNotices: jest.fn(() => []),
getDirectoryHeaderLinks: jest.fn(() => []),
getModuleNotices: jest.fn(() => []),
getCustomStatusCheck: jest.fn(),
getCustomComponent: jest.fn(),
};
service.setup.mockImplementation(createSetupMock);
return service;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,44 @@ describe('TutorialService', () => {
expect(service.getModuleNotices()).toEqual(notices);
});
});

describe('custom status check', () => {
test('returns undefined when name is customStatusCheckName is empty', () => {
const service = new TutorialService();
expect(service.getCustomStatusCheck('')).toBeUndefined();
});
test('returns undefined when custom status check was not registered', () => {
const service = new TutorialService();
expect(service.getCustomStatusCheck('foo')).toBeUndefined();
});
test('returns custom status check', () => {
const service = new TutorialService();
const callback = jest.fn();
service.setup().registerCustomStatusCheck('foo', callback);
const customStatusCheckCallback = service.getCustomStatusCheck('foo');
expect(customStatusCheckCallback).toBeDefined();
customStatusCheckCallback();
expect(callback).toHaveBeenCalled();
});
});

describe('custom component', () => {
test('returns undefined when name is customComponentName is empty', () => {
const service = new TutorialService();
expect(service.getCustomComponent('')).toBeUndefined();
});
test('returns undefined when custom component was not registered', () => {
const service = new TutorialService();
expect(service.getCustomComponent('foo')).toBeUndefined();
});
test('returns custom component', async () => {
const service = new TutorialService();
const customComponent = <div>foo</div>;
service.setup().registerCustomComponent('foo', async () => customComponent);
const customStatusCheckCallback = service.getCustomComponent('foo');
expect(customStatusCheckCallback).toBeDefined();
const result = await customStatusCheckCallback();
expect(result).toEqual(customComponent);
});
});
});
Loading