Skip to content

Commit

Permalink
Merge branch 'main' into 159040-add-config-feature-flags
Browse files Browse the repository at this point in the history
  • Loading branch information
MiriamAparicio authored Jun 19, 2023
2 parents 85c56fe + c131f41 commit 2091bf7
Show file tree
Hide file tree
Showing 34 changed files with 629 additions and 93 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

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

export const emptyTitleText = i18n.translate('visualizationUiComponents.emptyTitle', {
defaultMessage: '[Untitled]',
});
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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import React from 'react';
import { DimensionButton, DimensionButtonProps } from './dimension_button';

describe('DimensionButton', () => {
function getDefaultProps(): Omit<DimensionButtonProps, 'label' | 'children'> {
return {
groupLabel: 'myGroup',
onClick: jest.fn(),
onRemoveClick: jest.fn(),
accessorConfig: { columnId: '1' },
message: undefined,
};
}
it('should fallback to the empty title if the dimension label is made of an empty string', () => {
render(
<DimensionButton {...getDefaultProps()} label="">
<div />
</DimensionButton>
);
expect(screen.getByTitle('Edit [Untitled] configuration')).toBeInTheDocument();
});

it('should fallback to the empty title if the dimension label is made up of whitespaces only', () => {
render(
<DimensionButton {...getDefaultProps()} label=" ">
<div />
</DimensionButton>
);
expect(screen.getByTitle('Edit [Untitled] configuration')).toBeInTheDocument();
});

it('should not fallback to the empty title if the dimension label has also valid chars beside whitespaces', () => {
render(
<DimensionButton {...getDefaultProps()} label="aaa ">
<div />
</DimensionButton>
);
expect(screen.getByTitle('Edit aaa configuration')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,27 @@ import { euiThemeVars } from '@kbn/ui-theme';
import { DimensionButtonIcon } from './dimension_button_icon';
import { PaletteIndicator } from './palette_indicator';
import type { AccessorConfig, Message } from './types';
import { emptyTitleText } from './constants';

const triggerLinkA11yText = (label: string) =>
i18n.translate('visualizationUiComponents.dimensionButton.editConfig', {
defaultMessage: 'Edit {label} configuration',
values: { label },
values: {
label: label.trim().length ? label : emptyTitleText,
},
});

export interface DimensionButtonProps {
className?: string;
groupLabel: string;
children: React.ReactElement;
onClick: (id: string) => void;
onRemoveClick: (id: string) => void;
accessorConfig: AccessorConfig;
label: string;
message?: Message;
}

export function DimensionButton({
groupLabel,
children,
Expand All @@ -37,16 +51,7 @@ export function DimensionButton({
label,
message,
...otherProps // from Drag&Drop integration
}: {
className?: string;
groupLabel: string;
children: React.ReactElement;
onClick: (id: string) => void;
onRemoveClick: (id: string) => void;
accessorConfig: AccessorConfig;
label: string;
message?: Message;
}) {
}: DimensionButtonProps) {
return (
<div
{...otherProps}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export * from './constants';
export * from './dimension_button';

export * from './empty_button';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import React from 'react';
import { DimensionTrigger } from './trigger';

describe('DimensionTrigger', () => {
it('should fallback to the empty title if the dimension label is made of an empty string', () => {
render(<DimensionTrigger id="dimensionEmpty" label="" />);
expect(screen.queryByText('[Untitled]')).toBeInTheDocument();
});

it('should fallback to the empty title if the dimension label is made up of whitespaces only', () => {
render(<DimensionTrigger id="dimensionEmpty" label=" " />);
expect(screen.queryByText('[Untitled]')).toBeInTheDocument();
});

it('should not fallback to the empty title if the dimension label has also valid chars beside whitespaces', () => {
render(<DimensionTrigger id="dimensionEmpty" label="aaa " />);
expect(screen.queryByText('aaa')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
import { EuiText, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { EuiTextProps } from '@elastic/eui/src/components/text/text';
import type { EuiTextProps } from '@elastic/eui/src/components/text/text';
import { css } from '@emotion/react';
import { euiThemeVars } from '@kbn/ui-theme';
import { emptyTitleText } from './constants';

export const defaultDimensionTriggerTooltip = (
<p>
Expand All @@ -36,6 +37,10 @@ export const DimensionTrigger = ({
color?: EuiTextProps['color'];
dataTestSubj?: string;
}) => {
let safeLabel = label;
if (typeof label === 'string') {
safeLabel = label?.trim().length > 0 ? label : emptyTitleText;
}
return (
<EuiText
size="s"
Expand All @@ -61,7 +66,7 @@ export const DimensionTrigger = ({
}
`}
>
{label}
{safeLabel}
</span>
</span>
</EuiFlexItem>
Expand Down
1 change: 1 addition & 0 deletions src/plugins/visualization_ui_components/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export {
EmptyDimensionButton,
LineStyleSettings,
TextDecorationSetting,
emptyTitleText,
} from './components';

export { isFieldLensCompatible } from './util';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
* 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 { apm, timerange } from '@kbn/apm-synthtrace-client';
import { synthtrace } from '../../../../synthtrace';

const start = Date.now() - 1000;
const end = Date.now();

function generateData({ from, to }: { from: number; to: number }) {
const range = timerange(from, to);
const synthGo1 = apm
.service({
name: 'synth-go-1',
environment: 'production',
agentName: 'go',
})
.instance('my-instance');

return range.interval('1m').generator((timestamp) => {
return [
synthGo1
.transaction({ transactionName: 'GET /apple 🍎' })
.timestamp(timestamp)
.duration(1000)
.success(),
];
});
}

describe('APM Onboarding', () => {
describe('General navigation', () => {
beforeEach(() => {
cy.loginAsEditorUser();
cy.visitKibana('/app/apm/onboarding');
});

it('includes section for APM Agents', () => {
cy.contains('APM Agents');
cy.contains('Node.js');
cy.contains('Django');
cy.contains('Flask');
cy.contains('Ruby on Rails');
cy.contains('Rack');
cy.contains('Go');
cy.contains('Java');
cy.contains('.NET');
cy.contains('PHP');
cy.contains('OpenTelemetry');
});

it('navigation to different Tabs', () => {
cy.contains('Django').click();
cy.contains('pip install elastic-apm');

cy.contains('Flask').click();
cy.contains('pip install elastic-apm[flask]');

cy.contains('Ruby on Rails').click();
cy.contains("gem 'elastic-apm'");

cy.contains('Rack').click();
cy.contains("gem 'elastic-apm'");

cy.contains('Go').click();
cy.contains('go get go.elastic.co/apm');

cy.contains('Java').click();
cy.contains('-javaagent');

cy.contains('.NET').click();
cy.contains('Elastic.Apm.NetCoreAll');

cy.contains('PHP').click();
cy.contains('apk add --allow-untrusted <package-file>.apk');

cy.contains('OpenTelemetry').click();
cy.contains('Download the OpenTelemetry APM Agent or SDK');
});
});

describe('check Agent Status', () => {
beforeEach(() => {
cy.loginAsEditorUser();
cy.visitKibana('/app/apm/onboarding');
});
it('when no data is present', () => {
cy.intercept('GET', '/internal/apm/observability_overview/has_data').as(
'hasData'
);
cy.getByTestSubj('checkAgentStatus').click();
cy.wait('@hasData');
cy.getByTestSubj('agentStatusWarningCallout').should('exist');
});

it('when data is present', () => {
synthtrace.index(
generateData({
from: new Date(start).getTime(),
to: new Date(end).getTime(),
})
);
cy.intercept('GET', '/internal/apm/observability_overview/has_data').as(
'hasData'
);
cy.getByTestSubj('checkAgentStatus').click();
cy.wait('@hasData');
cy.getByTestSubj('agentStatusSuccessCallout').should('exist');
synthtrace.clean();
});
});

describe('create API Key', () => {
it('create the key successfully', () => {
cy.loginAsApmManageOwnAndCreateAgentKeys();
cy.visitKibana('/app/apm/onboarding');
cy.intercept('POST', '/api/apm/agent_keys').as('createApiKey');
cy.getByTestSubj('createApiKeyAndId').click();
cy.wait('@createApiKey');
cy.getByTestSubj('apiKeySuccessCallout').should('exist');

cy.contains('Django').click();
cy.getByTestSubj('apiKeySuccessCallout').should('exist');

cy.contains('Flask').click();
cy.getByTestSubj('apiKeySuccessCallout').should('exist');

cy.contains('Ruby on Rails').click();
cy.getByTestSubj('apiKeySuccessCallout').should('exist');

cy.contains('Rack').click();
cy.getByTestSubj('apiKeySuccessCallout').should('exist');

cy.contains('Go').click();
cy.getByTestSubj('apiKeySuccessCallout').should('exist');

cy.contains('Java').click();
cy.getByTestSubj('apiKeySuccessCallout').should('exist');

cy.contains('.NET').click();
cy.getByTestSubj('apiKeySuccessCallout').should('exist');

cy.contains('PHP').click();
cy.getByTestSubj('apiKeySuccessCallout').should('exist');

cy.contains('OpenTelemetry').click();
cy.getByTestSubj('apiKeySuccessCallout').should('exist');
});

it('fails to create the key due to missing privileges', () => {
cy.loginAsEditorUser();
cy.visitKibana('/app/apm/onboarding');
cy.intercept('POST', '/api/apm/agent_keys').as('createApiKey');
cy.getByTestSubj('createApiKeyAndId').click();
cy.wait('@createApiKey');
cy.getByTestSubj('apiKeyWarningCallout').should('exist');
cy.get('@createApiKey')
.its('response')
.then((res) => {
expect(res.statusCode).to.equal(403);
});
});
});
});
7 changes: 7 additions & 0 deletions x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ Cypress.Commands.add('loginAsMonitorUser', () => {
});
});

Cypress.Commands.add('loginAsApmManageOwnAndCreateAgentKeys', () => {
return cy.loginAs({
username: ApmUsername.apmManageOwnAndCreateAgentKeys,
password: 'changeme',
});
});

Cypress.Commands.add(
'loginAs',
({ username, password }: { username: string; password: string }) => {
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ declare namespace Cypress {
loginAsViewerUser(): Cypress.Chainable<Cypress.Response<any>>;
loginAsEditorUser(): Cypress.Chainable<Cypress.Response<any>>;
loginAsMonitorUser(): Cypress.Chainable<Cypress.Response<any>>;
loginAsApmManageOwnAndCreateAgentKeys(): Cypress.Chainable<
Cypress.Response<any>
>;
loginAs(params: {
username: string;
password: string;
Expand Down
Loading

0 comments on commit 2091bf7

Please sign in to comment.