From a500dedb69228a13810c8fdb93c08da1255aee26 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 11 Feb 2019 08:39:07 -0500 Subject: [PATCH 01/34] hide infra/logs apps if disabled via UICapabilities --- src/ui/public/capabilities/react/index.ts | 23 +++ .../react/inject_ui_capabilities.tsx | 57 ++++++ .../react/inject_ui_capabilities_provider.tsx | 38 ++++ .../react/requires_ui_capability.tsx | 34 ++++ .../react/ui_capabilities_provider.tsx | 48 +++++ .../plugins/infra/public/apps/start_app.tsx | 35 ++-- .../components/waffle/node_context_menu.tsx | 173 ++++++++++-------- x-pack/plugins/infra/public/routes.tsx | 14 +- x-pack/plugins/infra/server/kibana.index.ts | 4 +- .../security_and_spaces/tests/infra.ts | 87 +++++++++ 10 files changed, 412 insertions(+), 101 deletions(-) create mode 100644 src/ui/public/capabilities/react/index.ts create mode 100644 src/ui/public/capabilities/react/inject_ui_capabilities.tsx create mode 100644 src/ui/public/capabilities/react/inject_ui_capabilities_provider.tsx create mode 100644 src/ui/public/capabilities/react/requires_ui_capability.tsx create mode 100644 src/ui/public/capabilities/react/ui_capabilities_provider.tsx create mode 100644 x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts diff --git a/src/ui/public/capabilities/react/index.ts b/src/ui/public/capabilities/react/index.ts new file mode 100644 index 0000000000000..c35174db1c6e3 --- /dev/null +++ b/src/ui/public/capabilities/react/index.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { UICapabilitiesProvider } from './ui_capabilities_provider'; +export { injectUICapabilites } from './inject_ui_capabilities'; +export { injectUICapabilitiesProvider } from './inject_ui_capabilities_provider'; +export { RequiresUICapability } from './requires_ui_capability'; diff --git a/src/ui/public/capabilities/react/inject_ui_capabilities.tsx b/src/ui/public/capabilities/react/inject_ui_capabilities.tsx new file mode 100644 index 0000000000000..5beac2df97711 --- /dev/null +++ b/src/ui/public/capabilities/react/inject_ui_capabilities.tsx @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import PropTypes from 'prop-types'; +import React, { Component, ComponentClass, ComponentType } from 'react'; +import { UICapabilities } from '../ui_capabilities'; + +function getDisplayName(component: ComponentType) { + return component.displayName || component.name || 'Component'; +} + +interface InjectedProps { + uiCapabilities: UICapabilities; +} + +export function injectUICapabilites

( + WrappedComponent: ComponentType

+): ComponentClass>> & { + WrappedComponent: ComponentType

; +} { + class InjectUICapabilites extends Component { + public static displayName = `InjectUICapabilities(${getDisplayName(WrappedComponent)})`; + + public static WrappedComponent: ComponentType

= WrappedComponent; + + public static contextTypes = { + uiCapabilities: PropTypes.object.isRequired, + }; + + constructor(props: any, context: any) { + super(props, context); + } + + public render() { + return ( + + ); + } + } + return InjectUICapabilites; +} diff --git a/src/ui/public/capabilities/react/inject_ui_capabilities_provider.tsx b/src/ui/public/capabilities/react/inject_ui_capabilities_provider.tsx new file mode 100644 index 0000000000000..9a5e1b20d8a35 --- /dev/null +++ b/src/ui/public/capabilities/react/inject_ui_capabilities_provider.tsx @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; + +import { UICapabilitiesProvider } from './ui_capabilities_provider'; + +export function injectUICapabilitiesProvider

(WrappedComponent: React.ComponentType

) { + const UICapabilitiesProviderWrapper: React.SFC

= props => { + return ( + + + + ); + }; + + // Original propTypes from the wrapped component should be re-exposed + // since it will be used by reactDirective Angular service + // that will rely on propTypes to watch attributes with these names + UICapabilitiesProviderWrapper.propTypes = WrappedComponent.propTypes; + + return UICapabilitiesProviderWrapper; +} diff --git a/src/ui/public/capabilities/react/requires_ui_capability.tsx b/src/ui/public/capabilities/react/requires_ui_capability.tsx new file mode 100644 index 0000000000000..c78c3e75b4ccc --- /dev/null +++ b/src/ui/public/capabilities/react/requires_ui_capability.tsx @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import _ from 'lodash'; +import { UICapabilities } from '../ui_capabilities'; +import { injectUICapabilites } from './inject_ui_capabilities'; + +interface Props { + uiCapability: string; + uiCapabilities: UICapabilities; + children: React.ReactElement; +} + +export const RequiresUICapability = injectUICapabilites((props: Props) => { + if (_.get(props.uiCapabilities, props.uiCapability)) { + return props.children; + } + return null; +}); diff --git a/src/ui/public/capabilities/react/ui_capabilities_provider.tsx b/src/ui/public/capabilities/react/ui_capabilities_provider.tsx new file mode 100644 index 0000000000000..cda046fd162c5 --- /dev/null +++ b/src/ui/public/capabilities/react/ui_capabilities_provider.tsx @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import PropTypes from 'prop-types'; +import React, { ReactNode } from 'react'; +import { uiCapabilities, UICapabilities } from '../ui_capabilities'; + +interface Props { + children: ReactNode; +} + +interface ProviderContext { + uiCapabilities: UICapabilities; +} + +export class UICapabilitiesProvider extends React.Component { + public static displayName: string = 'UICapabilitiesProvider'; + + public static childContextTypes = { + uiCapabilities: PropTypes.object.isRequired, + }; + + public getChildContext(): ProviderContext { + return { + uiCapabilities, + }; + } + + public render() { + return React.Children.only(this.props.children); + } +} diff --git a/x-pack/plugins/infra/public/apps/start_app.tsx b/x-pack/plugins/infra/public/apps/start_app.tsx index b3d67511a6172..cf70061a240b9 100644 --- a/x-pack/plugins/infra/public/apps/start_app.tsx +++ b/x-pack/plugins/infra/public/apps/start_app.tsx @@ -17,6 +17,7 @@ import { ThemeProvider } from 'styled-components'; import { EuiErrorBoundary } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { UICapabilitiesProvider } from 'ui/capabilities/react'; import { I18nContext } from 'ui/i18n'; import { InfraFrontendLibs } from '../lib/lib'; import { PageRouter } from '../routes'; @@ -33,22 +34,24 @@ export async function startApp(libs: InfraFrontendLibs) { libs.framework.render( - - - - - ({ - eui: libs.framework.darkMode ? euiDarkVars : euiLightVars, - darkMode: libs.framework.darkMode, - })} - > - - - - - - + + + + + + ({ + eui: libs.framework.darkMode ? euiDarkVars : euiLightVars, + darkMode: libs.framework.darkMode, + })} + > + + + + + + + ); } diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index 1e8f64ae5c1bf..2a707806e88aa 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -7,7 +7,8 @@ import { EuiContextMenu, EuiContextMenuPanelDescriptor, EuiPopover } from '@elastic/eui'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React from 'react'; - +import { UICapabilities } from 'ui/capabilities'; +import { injectUICapabilites } from 'ui/capabilities/react'; import { InfraNodeType, InfraTimerangeInput } from '../../graphql/types'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib'; import { getNodeDetailUrl, getNodeLogsUrl } from '../../pages/link_to'; @@ -21,89 +22,103 @@ interface Props { isPopoverOpen: boolean; closePopover: () => void; intl: InjectedIntl; + uiCapabilities: UICapabilities; } -export const NodeContextMenu = injectI18n( - ({ options, timeRange, children, node, isPopoverOpen, closePopover, nodeType, intl }: Props) => { - // Due to the changing nature of the fields between APM and this UI, - // We need to have some exceptions until 7.0 & ECS is finalized. Reference - // #26620 for the details for these fields. - // TODO: This is tech debt, remove it after 7.0 & ECS migration. - const APM_FIELDS = { - [InfraNodeType.host]: 'context.system.hostname', - [InfraNodeType.container]: 'container.id', - [InfraNodeType.pod]: 'kubernetes.pod.uid', - }; +export const NodeContextMenu = injectUICapabilites( + injectI18n( + ({ + options, + timeRange, + children, + node, + isPopoverOpen, + closePopover, + nodeType, + intl, + uiCapabilities, + }: Props) => { + // Due to the changing nature of the fields between APM and this UI, + // We need to have some exceptions until 7.0 & ECS is finalized. Reference + // #26620 for the details for these fields. + // TODO: This is tech debt, remove it after 7.0 & ECS migration. + const APM_FIELDS = { + [InfraNodeType.host]: 'context.system.hostname', + [InfraNodeType.container]: 'container.id', + [InfraNodeType.pod]: 'kubernetes.pod.uid', + }; + + const nodeLogsUrl = + node.id && uiCapabilities.logs.show + ? getNodeLogsUrl({ + nodeType, + nodeId: node.id, + time: timeRange.to, + }) + : undefined; + const nodeDetailUrl = node.id + ? getNodeDetailUrl({ + nodeType, + nodeId: node.id, + from: timeRange.from, + to: timeRange.to, + }) + : undefined; - const nodeLogsUrl = node.id - ? getNodeLogsUrl({ - nodeType, - nodeId: node.id, - time: timeRange.to, - }) - : undefined; - const nodeDetailUrl = node.id - ? getNodeDetailUrl({ - nodeType, - nodeId: node.id, - from: timeRange.from, - to: timeRange.to, - }) - : undefined; + const apmTracesUrl = { + name: intl.formatMessage( + { + id: 'xpack.infra.nodeContextMenu.viewAPMTraces', + defaultMessage: 'View {nodeType} APM traces', + }, + { nodeType } + ), + href: `../app/apm#/traces?_g=()&kuery=${APM_FIELDS[nodeType]}~20~3A~20~22${node.id}~22`, + }; - const apmTracesUrl = { - name: intl.formatMessage( + const panels: EuiContextMenuPanelDescriptor[] = [ { - id: 'xpack.infra.nodeContextMenu.viewAPMTraces', - defaultMessage: 'View {nodeType} APM traces', + id: 0, + title: '', + items: [ + ...(nodeLogsUrl + ? [ + { + name: intl.formatMessage({ + id: 'xpack.infra.nodeContextMenu.viewLogsName', + defaultMessage: 'View logs', + }), + href: nodeLogsUrl, + }, + ] + : []), + ...(nodeDetailUrl + ? [ + { + name: intl.formatMessage({ + id: 'xpack.infra.nodeContextMenu.viewMetricsName', + defaultMessage: 'View metrics', + }), + href: nodeDetailUrl, + }, + ] + : []), + ...[apmTracesUrl], + ], }, - { nodeType } - ), - href: `../app/apm#/traces?_g=()&kuery=${APM_FIELDS[nodeType]}~20~3A~20~22${node.id}~22`, - }; - - const panels: EuiContextMenuPanelDescriptor[] = [ - { - id: 0, - title: '', - items: [ - ...(nodeLogsUrl - ? [ - { - name: intl.formatMessage({ - id: 'xpack.infra.nodeContextMenu.viewLogsName', - defaultMessage: 'View logs', - }), - href: nodeLogsUrl, - }, - ] - : []), - ...(nodeDetailUrl - ? [ - { - name: intl.formatMessage({ - id: 'xpack.infra.nodeContextMenu.viewMetricsName', - defaultMessage: 'View metrics', - }), - href: nodeDetailUrl, - }, - ] - : []), - ...[apmTracesUrl], - ], - }, - ]; + ]; - return ( - - - - ); - } + return ( + + + + ); + } + ) ); diff --git a/x-pack/plugins/infra/public/routes.tsx b/x-pack/plugins/infra/public/routes.tsx index ca23ebae2279f..598c835aea373 100644 --- a/x-pack/plugins/infra/public/routes.tsx +++ b/x-pack/plugins/infra/public/routes.tsx @@ -8,6 +8,8 @@ import { History } from 'history'; import React from 'react'; import { Redirect, Route, Router, Switch } from 'react-router-dom'; +import { UICapabilities } from 'ui/capabilities'; +import { injectUICapabilites } from 'ui/capabilities/react'; import { NotFoundPage } from './pages/404'; import { HomePage } from './pages/home'; import { LinkToPage } from './pages/link_to'; @@ -16,15 +18,17 @@ import { MetricDetail } from './pages/metrics'; interface RouterProps { history: History; + uiCapabilities: UICapabilities; } -export const PageRouter: React.SFC = ({ history }) => { +const PageRouterComponent: React.SFC = ({ history, uiCapabilities }) => { + const defaultRoute = uiCapabilities.infrastructure.show ? '/home' : '/logs'; return ( - - - + + {uiCapabilities.logs.show && } + {uiCapabilities.infrastructure.show && } @@ -32,3 +36,5 @@ export const PageRouter: React.SFC = ({ history }) => { ); }; + +export const PageRouter = injectUICapabilites<{ history: History }>(PageRouterComponent); diff --git a/x-pack/plugins/infra/server/kibana.index.ts b/x-pack/plugins/infra/server/kibana.index.ts index 18277624f7cb7..01352ca06a71d 100644 --- a/x-pack/plugins/infra/server/kibana.index.ts +++ b/x-pack/plugins/infra/server/kibana.index.ts @@ -38,7 +38,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { all: [], read: ['config'], }, - ui: [], + ui: ['show'], }, }, }); @@ -58,7 +58,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { all: [], read: ['config'], }, - ui: [], + ui: ['show'], }, }, }); diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts new file mode 100644 index 0000000000000..33973019e29a3 --- /dev/null +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts @@ -0,0 +1,87 @@ +/* + * 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 expect from 'expect.js'; +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; +import { + GetUICapabilitiesFailureReason, + UICapabilitiesService, +} from '../../common/services/ui_capabilities'; +import { UserAtSpaceScenarios } from '../scenarios'; + +// tslint:disable:no-default-export +export default function infraTests({ getService }: KibanaFunctionalTestDefaultProviders) { + const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities'); + + describe('infra-ui', () => { + UserAtSpaceScenarios.forEach(scenario => { + it(`${scenario.id}`, async () => { + const { user, space } = scenario; + + const uiCapabilities = await uiCapabilitiesService.get( + { username: user.username, password: user.password }, + space.id + ); + switch (scenario.id) { + // these users have a read/write view of Infra + case 'superuser at everything_space': + case 'global_all at everything_space': + case 'dual_privileges_all at everything_space': + case 'everything_space_all at everything_space': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('infra'); + expect(uiCapabilities.value!.infra).to.eql({ + show: true, + }); + break; + // these users have a read only view of Infra + case 'global_read at everything_space': + case 'dual_privileges_read at everything_space': + case 'everything_space_read at everything_space': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('infra'); + expect(uiCapabilities.value!.infra).to.eql({ + show: true, + }); + break; + // the nothing_space has no features enabled, so even if we have + // privileges to perform these actions, we won't be able to + case 'superuser at nothing_space': + case 'global_all at nothing_space': + case 'global_read at nothing_space': + case 'dual_privileges_all at nothing_space': + case 'dual_privileges_read at nothing_space': + case 'nothing_space_all at nothing_space': + case 'nothing_space_read at nothing_space': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('infra'); + expect(uiCapabilities.value!.infra).to.eql({ + show: false, + }); + break; + // if we don't have access at the space itself, we're + // redirected to the space selector and the ui capabilities + // are lagely irrelevant because they won't be consumed + case 'no_kibana_privileges at everything_space': + case 'no_kibana_privileges at nothing_space': + case 'legacy_all at everything_space': + case 'legacy_all at nothing_space': + case 'everything_space_all at nothing_space': + case 'everything_space_read at nothing_space': + case 'nothing_space_all at everything_space': + case 'nothing_space_read at everything_space': + expect(uiCapabilities.success).to.be(false); + expect(uiCapabilities.failureReason).to.be( + GetUICapabilitiesFailureReason.RedirectedToRoot + ); + break; + default: + throw new UnreachableError(scenario); + } + }); + }); + }); +} From 2df2771064943361776b609bc70a7cdb81c039fe Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 11 Feb 2019 09:04:16 -0500 Subject: [PATCH 02/34] adds tests --- src/ui/public/capabilities/react/index.ts | 3 +- .../react/inject_ui_capabilities.test.tsx | 117 ++++++++++++++++ .../react/inject_ui_capabilities.tsx | 6 +- .../react/inject_ui_capabilities_provider.tsx | 38 ----- .../react/requires_ui_capability.test.tsx | 132 ++++++++++++++++++ .../react/requires_ui_capability.tsx | 4 +- .../components/waffle/node_context_menu.tsx | 4 +- x-pack/plugins/infra/public/routes.tsx | 4 +- 8 files changed, 259 insertions(+), 49 deletions(-) create mode 100644 src/ui/public/capabilities/react/inject_ui_capabilities.test.tsx delete mode 100644 src/ui/public/capabilities/react/inject_ui_capabilities_provider.tsx create mode 100644 src/ui/public/capabilities/react/requires_ui_capability.test.tsx diff --git a/src/ui/public/capabilities/react/index.ts b/src/ui/public/capabilities/react/index.ts index c35174db1c6e3..4310b10a25b13 100644 --- a/src/ui/public/capabilities/react/index.ts +++ b/src/ui/public/capabilities/react/index.ts @@ -18,6 +18,5 @@ */ export { UICapabilitiesProvider } from './ui_capabilities_provider'; -export { injectUICapabilites } from './inject_ui_capabilities'; -export { injectUICapabilitiesProvider } from './inject_ui_capabilities_provider'; +export { injectUICapabilities } from './inject_ui_capabilities'; export { RequiresUICapability } from './requires_ui_capability'; diff --git a/src/ui/public/capabilities/react/inject_ui_capabilities.test.tsx b/src/ui/public/capabilities/react/inject_ui_capabilities.test.tsx new file mode 100644 index 0000000000000..48441c94a031f --- /dev/null +++ b/src/ui/public/capabilities/react/inject_ui_capabilities.test.tsx @@ -0,0 +1,117 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +jest.mock('ui/chrome', () => ({ + getInjected(key: string) { + if (key === 'uiCapabilities') { + return { + uiCapability1: true, + uiCapability2: { + nestedProp: 'nestedValue', + }, + }; + } + }, +})); + +import { mount } from 'enzyme'; +import React from 'react'; +import { UICapabilities } from '..'; +import { injectUICapabilities } from './inject_ui_capabilities'; +import { UICapabilitiesProvider } from './ui_capabilities_provider'; + +describe('injectUICapabilities', () => { + it('provides UICapabilities to SFCs', () => { + interface SFCProps { + uiCapabilities: UICapabilities; + } + + const MySFC = injectUICapabilities(({ uiCapabilities }: SFCProps) => { + return {uiCapabilities.uiCapability2.nestedProp}; + }); + + const wrapper = mount( + + + + ); + + expect(wrapper).toMatchInlineSnapshot(` + + + + + nestedValue + + + + +`); + }); + + it('provides UICapabilities to class components', () => { + interface ClassProps { + uiCapabilities: UICapabilities; + } + + class MyClassComponent extends React.Component { + public render() { + return {this.props.uiCapabilities.uiCapability2.nestedProp}; + } + } + + const WrappedComponent = injectUICapabilities(MyClassComponent); + + const wrapper = mount( + + + + ); + + expect(wrapper).toMatchInlineSnapshot(` + + + + + nestedValue + + + + +`); + }); +}); diff --git a/src/ui/public/capabilities/react/inject_ui_capabilities.tsx b/src/ui/public/capabilities/react/inject_ui_capabilities.tsx index 5beac2df97711..07583b85f725f 100644 --- a/src/ui/public/capabilities/react/inject_ui_capabilities.tsx +++ b/src/ui/public/capabilities/react/inject_ui_capabilities.tsx @@ -29,12 +29,12 @@ interface InjectedProps { uiCapabilities: UICapabilities; } -export function injectUICapabilites

( +export function injectUICapabilities

( WrappedComponent: ComponentType

): ComponentClass>> & { WrappedComponent: ComponentType

; } { - class InjectUICapabilites extends Component { + class InjectUICapabilities extends Component { public static displayName = `InjectUICapabilities(${getDisplayName(WrappedComponent)})`; public static WrappedComponent: ComponentType

= WrappedComponent; @@ -53,5 +53,5 @@ export function injectUICapabilites

( ); } } - return InjectUICapabilites; + return InjectUICapabilities; } diff --git a/src/ui/public/capabilities/react/inject_ui_capabilities_provider.tsx b/src/ui/public/capabilities/react/inject_ui_capabilities_provider.tsx deleted file mode 100644 index 9a5e1b20d8a35..0000000000000 --- a/src/ui/public/capabilities/react/inject_ui_capabilities_provider.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React from 'react'; - -import { UICapabilitiesProvider } from './ui_capabilities_provider'; - -export function injectUICapabilitiesProvider

(WrappedComponent: React.ComponentType

) { - const UICapabilitiesProviderWrapper: React.SFC

= props => { - return ( - - - - ); - }; - - // Original propTypes from the wrapped component should be re-exposed - // since it will be used by reactDirective Angular service - // that will rely on propTypes to watch attributes with these names - UICapabilitiesProviderWrapper.propTypes = WrappedComponent.propTypes; - - return UICapabilitiesProviderWrapper; -} diff --git a/src/ui/public/capabilities/react/requires_ui_capability.test.tsx b/src/ui/public/capabilities/react/requires_ui_capability.test.tsx new file mode 100644 index 0000000000000..f9cc7f8a55910 --- /dev/null +++ b/src/ui/public/capabilities/react/requires_ui_capability.test.tsx @@ -0,0 +1,132 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +jest.mock('ui/chrome', () => ({ + getInjected(key: string) { + if (key === 'uiCapabilities') { + return { + app: { + feature1: true, + feature2: false, + }, + }; + } + }, +})); + +import { mount } from 'enzyme'; +import React from 'react'; +import { UICapabilitiesProvider } from '.'; +import { RequiresUICapability } from './requires_ui_capability'; + +describe('', () => { + it('renders the child if the UI Capability is satisfied', () => { + const wrapper = mount( + + +

this renders
+ + + ); + + expect(wrapper).toMatchInlineSnapshot(` + + + +
+ this renders +
+
+
+
+`); + }); + + it('does not render the child if the UI Capability is not satisfied', () => { + const wrapper = mount( + + +
this does not render
+
+
+ ); + + expect(wrapper).toMatchInlineSnapshot(` + + + + + +`); + }); + + it('does not render the child if the UI Capability is not defined', () => { + const wrapper = mount( + + +
this does not render
+
+
+ ); + + expect(wrapper).toMatchInlineSnapshot(` + + + + + +`); + }); +}); diff --git a/src/ui/public/capabilities/react/requires_ui_capability.tsx b/src/ui/public/capabilities/react/requires_ui_capability.tsx index c78c3e75b4ccc..6fa45ad3dd1c6 100644 --- a/src/ui/public/capabilities/react/requires_ui_capability.tsx +++ b/src/ui/public/capabilities/react/requires_ui_capability.tsx @@ -18,7 +18,7 @@ */ import _ from 'lodash'; import { UICapabilities } from '../ui_capabilities'; -import { injectUICapabilites } from './inject_ui_capabilities'; +import { injectUICapabilities } from './inject_ui_capabilities'; interface Props { uiCapability: string; @@ -26,7 +26,7 @@ interface Props { children: React.ReactElement; } -export const RequiresUICapability = injectUICapabilites((props: Props) => { +export const RequiresUICapability = injectUICapabilities((props: Props) => { if (_.get(props.uiCapabilities, props.uiCapability)) { return props.children; } diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index 2a707806e88aa..aad61aff170fb 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -8,7 +8,7 @@ import { EuiContextMenu, EuiContextMenuPanelDescriptor, EuiPopover } from '@elas import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React from 'react'; import { UICapabilities } from 'ui/capabilities'; -import { injectUICapabilites } from 'ui/capabilities/react'; +import { injectUICapabilities } from 'ui/capabilities/react'; import { InfraNodeType, InfraTimerangeInput } from '../../graphql/types'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib'; import { getNodeDetailUrl, getNodeLogsUrl } from '../../pages/link_to'; @@ -25,7 +25,7 @@ interface Props { uiCapabilities: UICapabilities; } -export const NodeContextMenu = injectUICapabilites( +export const NodeContextMenu = injectUICapabilities( injectI18n( ({ options, diff --git a/x-pack/plugins/infra/public/routes.tsx b/x-pack/plugins/infra/public/routes.tsx index 598c835aea373..072f1be0afa2e 100644 --- a/x-pack/plugins/infra/public/routes.tsx +++ b/x-pack/plugins/infra/public/routes.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { Redirect, Route, Router, Switch } from 'react-router-dom'; import { UICapabilities } from 'ui/capabilities'; -import { injectUICapabilites } from 'ui/capabilities/react'; +import { injectUICapabilities } from 'ui/capabilities/react'; import { NotFoundPage } from './pages/404'; import { HomePage } from './pages/home'; import { LinkToPage } from './pages/link_to'; @@ -37,4 +37,4 @@ const PageRouterComponent: React.SFC = ({ history, uiCapabilities } ); }; -export const PageRouter = injectUICapabilites<{ history: History }>(PageRouterComponent); +export const PageRouter = injectUICapabilities<{ history: History }>(PageRouterComponent); From b1b7c4157d12f4e41c77aafdb54185a67b0dd952 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 11 Feb 2019 09:31:55 -0500 Subject: [PATCH 03/34] adds UICapability tests for infra and log apps --- x-pack/plugins/infra/public/routes.tsx | 2 +- .../security_and_spaces/tests/index.ts | 2 + .../security_and_spaces/tests/infra.ts | 16 ++-- .../security_and_spaces/tests/logs.ts | 87 ++++++++++++++++++ .../security_only/tests/index.ts | 2 + .../security_only/tests/infra.ts | 88 +++++++++++++++++++ .../security_only/tests/logs.ts | 88 +++++++++++++++++++ .../spaces_only/tests/index.ts | 2 + .../spaces_only/tests/infra.ts | 58 ++++++++++++ .../ui_capabilities/spaces_only/tests/logs.ts | 58 ++++++++++++ 10 files changed, 394 insertions(+), 9 deletions(-) create mode 100644 x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts create mode 100644 x-pack/test/ui_capabilities/security_only/tests/infra.ts create mode 100644 x-pack/test/ui_capabilities/security_only/tests/logs.ts create mode 100644 x-pack/test/ui_capabilities/spaces_only/tests/infra.ts create mode 100644 x-pack/test/ui_capabilities/spaces_only/tests/logs.ts diff --git a/x-pack/plugins/infra/public/routes.tsx b/x-pack/plugins/infra/public/routes.tsx index 072f1be0afa2e..62fc8d7e6e3c0 100644 --- a/x-pack/plugins/infra/public/routes.tsx +++ b/x-pack/plugins/infra/public/routes.tsx @@ -37,4 +37,4 @@ const PageRouterComponent: React.SFC = ({ history, uiCapabilities } ); }; -export const PageRouter = injectUICapabilities<{ history: History }>(PageRouterComponent); +export const PageRouter = injectUICapabilities(PageRouterComponent); diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts index d8266039174f8..52d50d575a5bb 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts @@ -58,6 +58,8 @@ export default function uiCapabilitesTests({ loadTestFile(require.resolve('./canvas')); loadTestFile(require.resolve('./dashboard')); loadTestFile(require.resolve('./discover')); + loadTestFile(require.resolve('./infra')); + loadTestFile(require.resolve('./logs')); loadTestFile(require.resolve('./maps')); loadTestFile(require.resolve('./nav_links')); loadTestFile(require.resolve('./timelion')); diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts index 33973019e29a3..8e414e4b7bd38 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts @@ -13,10 +13,10 @@ import { import { UserAtSpaceScenarios } from '../scenarios'; // tslint:disable:no-default-export -export default function infraTests({ getService }: KibanaFunctionalTestDefaultProviders) { +export default function infrastructureTests({ getService }: KibanaFunctionalTestDefaultProviders) { const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities'); - describe('infra-ui', () => { + describe('infrastructure-ui', () => { UserAtSpaceScenarios.forEach(scenario => { it(`${scenario.id}`, async () => { const { user, space } = scenario; @@ -32,8 +32,8 @@ export default function infraTests({ getService }: KibanaFunctionalTestDefaultPr case 'dual_privileges_all at everything_space': case 'everything_space_all at everything_space': expect(uiCapabilities.success).to.be(true); - expect(uiCapabilities.value).to.have.property('infra'); - expect(uiCapabilities.value!.infra).to.eql({ + expect(uiCapabilities.value).to.have.property('infrastructure'); + expect(uiCapabilities.value!.infrastructure).to.eql({ show: true, }); break; @@ -42,8 +42,8 @@ export default function infraTests({ getService }: KibanaFunctionalTestDefaultPr case 'dual_privileges_read at everything_space': case 'everything_space_read at everything_space': expect(uiCapabilities.success).to.be(true); - expect(uiCapabilities.value).to.have.property('infra'); - expect(uiCapabilities.value!.infra).to.eql({ + expect(uiCapabilities.value).to.have.property('infrastructure'); + expect(uiCapabilities.value!.infrastructure).to.eql({ show: true, }); break; @@ -57,8 +57,8 @@ export default function infraTests({ getService }: KibanaFunctionalTestDefaultPr case 'nothing_space_all at nothing_space': case 'nothing_space_read at nothing_space': expect(uiCapabilities.success).to.be(true); - expect(uiCapabilities.value).to.have.property('infra'); - expect(uiCapabilities.value!.infra).to.eql({ + expect(uiCapabilities.value).to.have.property('infrastructure'); + expect(uiCapabilities.value!.infrastructure).to.eql({ show: false, }); break; diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts new file mode 100644 index 0000000000000..384f303e05e42 --- /dev/null +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts @@ -0,0 +1,87 @@ +/* + * 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 expect from 'expect.js'; +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; +import { + GetUICapabilitiesFailureReason, + UICapabilitiesService, +} from '../../common/services/ui_capabilities'; +import { UserAtSpaceScenarios } from '../scenarios'; + +// tslint:disable:no-default-export +export default function logsTests({ getService }: KibanaFunctionalTestDefaultProviders) { + const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities'); + + describe('logs-ui', () => { + UserAtSpaceScenarios.forEach(scenario => { + it(`${scenario.id}`, async () => { + const { user, space } = scenario; + + const uiCapabilities = await uiCapabilitiesService.get( + { username: user.username, password: user.password }, + space.id + ); + switch (scenario.id) { + // these users have a read/write view of Logs + case 'superuser at everything_space': + case 'global_all at everything_space': + case 'dual_privileges_all at everything_space': + case 'everything_space_all at everything_space': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('logs'); + expect(uiCapabilities.value!.logs).to.eql({ + show: true, + }); + break; + // these users have a read only view of Logs + case 'global_read at everything_space': + case 'dual_privileges_read at everything_space': + case 'everything_space_read at everything_space': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('logs'); + expect(uiCapabilities.value!.logs).to.eql({ + show: true, + }); + break; + // the nothing_space has no features enabled, so even if we have + // privileges to perform these actions, we won't be able to + case 'superuser at nothing_space': + case 'global_all at nothing_space': + case 'global_read at nothing_space': + case 'dual_privileges_all at nothing_space': + case 'dual_privileges_read at nothing_space': + case 'nothing_space_all at nothing_space': + case 'nothing_space_read at nothing_space': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('logs'); + expect(uiCapabilities.value!.logs).to.eql({ + show: false, + }); + break; + // if we don't have access at the space itself, we're + // redirected to the space selector and the ui capabilities + // are lagely irrelevant because they won't be consumed + case 'no_kibana_privileges at everything_space': + case 'no_kibana_privileges at nothing_space': + case 'legacy_all at everything_space': + case 'legacy_all at nothing_space': + case 'everything_space_all at nothing_space': + case 'everything_space_read at nothing_space': + case 'nothing_space_all at everything_space': + case 'nothing_space_read at everything_space': + expect(uiCapabilities.success).to.be(false); + expect(uiCapabilities.failureReason).to.be( + GetUICapabilitiesFailureReason.RedirectedToRoot + ); + break; + default: + throw new UnreachableError(scenario); + } + }); + }); + }); +} diff --git a/x-pack/test/ui_capabilities/security_only/tests/index.ts b/x-pack/test/ui_capabilities/security_only/tests/index.ts index 92aa940a2bb4a..f04b6cbf420ba 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/index.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/index.ts @@ -47,6 +47,8 @@ export default function uiCapabilitesTests({ loadTestFile(require.resolve('./canvas')); loadTestFile(require.resolve('./dashboard')); loadTestFile(require.resolve('./discover')); + loadTestFile(require.resolve('./infra')); + loadTestFile(require.resolve('./logs')); loadTestFile(require.resolve('./maps')); loadTestFile(require.resolve('./nav_links')); loadTestFile(require.resolve('./timelion')); diff --git a/x-pack/test/ui_capabilities/security_only/tests/infra.ts b/x-pack/test/ui_capabilities/security_only/tests/infra.ts new file mode 100644 index 0000000000000..6591752efe5fd --- /dev/null +++ b/x-pack/test/ui_capabilities/security_only/tests/infra.ts @@ -0,0 +1,88 @@ +/* + * 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 expect from 'expect.js'; +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; +import { + GetUICapabilitiesFailureReason, + UICapabilitiesService, +} from '../../common/services/ui_capabilities'; +import { UserScenarios } from '../scenarios'; + +// tslint:disable:no-default-export +export default function advancedSettingsTests({ + getService, +}: KibanaFunctionalTestDefaultProviders) { + const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities'); + + describe('infrastructure', () => { + UserScenarios.forEach(scenario => { + it(`${scenario.fullName}`, async () => { + const uiCapabilities = await uiCapabilitiesService.get({ + username: scenario.username, + password: scenario.password, + }); + switch (scenario.username) { + // these users have a read/write view of Infrastructure + case 'superuser': + case 'all': + case 'dual_privileges_all': + case 'infrastructure_read': // TODO: awaiting read/write privilege for infra + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('infrastructure'); + expect(uiCapabilities.value!.infrastructure).to.eql({ + show: true, + }); + break; + // these users have a read-only view of Advanced Settings + case 'dual_privileges_read': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('infrastructure'); + expect(uiCapabilities.value!.infrastructure).to.eql({ + show: true, + }); + break; + // these users can't do anything with Advanced Settings + case 'advancedSettings_all': + case 'advancedSettings_read': + case 'apm_all': + case 'canvas_all': + case 'canvas_read': + case 'dev_tools_read': + case 'dashboard_all': + case 'dashboard_read': + case 'discover_all': + case 'discover_read': + case 'graph_all': + case 'graph_read': + case 'maps_all': + case 'maps_read': + case 'logs_read': + case 'ml_all': + case 'monitoring_all': + case 'timelion_all': + case 'timelion_read': + case 'uptime_read': + case 'visualize_all': + case 'visualize_read': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('infrastructure'); + expect(uiCapabilities.value!.infrastructure).to.eql({ + show: false, + }); + break; + case 'legacy_all': + case 'no_kibana_privileges': + expect(uiCapabilities.success).to.be(false); + expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); + break; + default: + throw new UnreachableError(scenario); + } + }); + }); + }); +} diff --git a/x-pack/test/ui_capabilities/security_only/tests/logs.ts b/x-pack/test/ui_capabilities/security_only/tests/logs.ts new file mode 100644 index 0000000000000..2f37a4181f208 --- /dev/null +++ b/x-pack/test/ui_capabilities/security_only/tests/logs.ts @@ -0,0 +1,88 @@ +/* + * 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 expect from 'expect.js'; +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; +import { + GetUICapabilitiesFailureReason, + UICapabilitiesService, +} from '../../common/services/ui_capabilities'; +import { UserScenarios } from '../scenarios'; + +// tslint:disable:no-default-export +export default function advancedSettingsTests({ + getService, +}: KibanaFunctionalTestDefaultProviders) { + const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities'); + + describe('logs', () => { + UserScenarios.forEach(scenario => { + it(`${scenario.fullName}`, async () => { + const uiCapabilities = await uiCapabilitiesService.get({ + username: scenario.username, + password: scenario.password, + }); + switch (scenario.username) { + // these users have a read/write view of Infrastructure + case 'superuser': + case 'all': + case 'dual_privileges_all': + case 'logs_read': // TODO: awaiting read/write privilege for logs + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('logs'); + expect(uiCapabilities.value!.logs).to.eql({ + show: true, + }); + break; + // these users have a read-only view of Advanced Settings + case 'dual_privileges_read': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('logs'); + expect(uiCapabilities.value!.logs).to.eql({ + show: true, + }); + break; + // these users can't do anything with Advanced Settings + case 'advancedSettings_all': + case 'advancedSettings_read': + case 'apm_all': + case 'canvas_all': + case 'canvas_read': + case 'dev_tools_read': + case 'dashboard_all': + case 'dashboard_read': + case 'discover_all': + case 'discover_read': + case 'graph_all': + case 'graph_read': + case 'infrastructure_read': + case 'maps_all': + case 'maps_read': + case 'ml_all': + case 'monitoring_all': + case 'timelion_all': + case 'timelion_read': + case 'uptime_read': + case 'visualize_all': + case 'visualize_read': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('logs'); + expect(uiCapabilities.value!.logs).to.eql({ + show: false, + }); + break; + case 'legacy_all': + case 'no_kibana_privileges': + expect(uiCapabilities.success).to.be(false); + expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); + break; + default: + throw new UnreachableError(scenario); + } + }); + }); + }); +} diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/index.ts b/x-pack/test/ui_capabilities/spaces_only/tests/index.ts index 1528b60dbf7ab..5dc81610df6d9 100644 --- a/x-pack/test/ui_capabilities/spaces_only/tests/index.ts +++ b/x-pack/test/ui_capabilities/spaces_only/tests/index.ts @@ -34,6 +34,8 @@ export default function uiCapabilitesTests({ loadTestFile(require.resolve('./canvas')); loadTestFile(require.resolve('./dashboard')); loadTestFile(require.resolve('./discover')); + loadTestFile(require.resolve('./infra')); + loadTestFile(require.resolve('./logs')); loadTestFile(require.resolve('./maps')); loadTestFile(require.resolve('./nav_links')); loadTestFile(require.resolve('./timelion')); diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/infra.ts b/x-pack/test/ui_capabilities/spaces_only/tests/infra.ts new file mode 100644 index 0000000000000..01e95aa1e1516 --- /dev/null +++ b/x-pack/test/ui_capabilities/spaces_only/tests/infra.ts @@ -0,0 +1,58 @@ +/* + * 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 expect from 'expect.js'; +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; +import { UICapabilitiesService } from '../../common/services/ui_capabilities'; +import { SpaceScenarios } from '../scenarios'; + +// tslint:disable:no-default-export +export default function advancedSettingsTests({ + getService, +}: KibanaFunctionalTestDefaultProviders) { + const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities'); + + describe('infrastructure', () => { + SpaceScenarios.forEach(scenario => { + it(`${scenario.name}`, async () => { + const uiCapabilities = await uiCapabilitiesService.get(null, scenario.id); + switch (scenario.id) { + case 'advanced_settings_disabled_space': + case 'everything_space': + case 'apm_disabled_space': + case 'dashboard_disabled_space': + case 'canvas_disabled_space': + case 'dev_tools_disabled_space': + case 'discover_disabled_space': + case 'graph_disabled_space': + case 'maps_disabled_space': + case 'logs_disabled_space': + case 'ml_disabled_space': + case 'monitoring_disabled_space': + case 'timelion_disabled_space': + case 'uptime_disabled_space': + case 'visualize_disabled_space': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('infrastructure'); + expect(uiCapabilities.value!.infrastructure).to.eql({ + show: true, + }); + break; + case 'nothing_space': + case 'infrastructure_disabled_space': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('infrastructure'); + expect(uiCapabilities.value!.infrastructure).to.eql({ + show: false, + }); + break; + default: + throw new UnreachableError(scenario); + } + }); + }); + }); +} diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/logs.ts b/x-pack/test/ui_capabilities/spaces_only/tests/logs.ts new file mode 100644 index 0000000000000..04283ee296f4d --- /dev/null +++ b/x-pack/test/ui_capabilities/spaces_only/tests/logs.ts @@ -0,0 +1,58 @@ +/* + * 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 expect from 'expect.js'; +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; +import { UICapabilitiesService } from '../../common/services/ui_capabilities'; +import { SpaceScenarios } from '../scenarios'; + +// tslint:disable:no-default-export +export default function advancedSettingsTests({ + getService, +}: KibanaFunctionalTestDefaultProviders) { + const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities'); + + describe('logs', () => { + SpaceScenarios.forEach(scenario => { + it(`${scenario.name}`, async () => { + const uiCapabilities = await uiCapabilitiesService.get(null, scenario.id); + switch (scenario.id) { + case 'advanced_settings_disabled_space': + case 'everything_space': + case 'apm_disabled_space': + case 'dashboard_disabled_space': + case 'canvas_disabled_space': + case 'dev_tools_disabled_space': + case 'discover_disabled_space': + case 'graph_disabled_space': + case 'infrastructure_disabled_space': + case 'maps_disabled_space': + case 'ml_disabled_space': + case 'monitoring_disabled_space': + case 'timelion_disabled_space': + case 'uptime_disabled_space': + case 'visualize_disabled_space': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('logs'); + expect(uiCapabilities.value!.logs).to.eql({ + show: true, + }); + break; + case 'nothing_space': + case 'logs_disabled_space': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('logs'); + expect(uiCapabilities.value!.logs).to.eql({ + show: false, + }); + break; + default: + throw new UnreachableError(scenario); + } + }); + }); + }); +} From fe63b39655b257facf913840bd69b08bb482ccc4 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 11 Feb 2019 10:41:03 -0500 Subject: [PATCH 04/34] update expected privilege/action mapping --- .../test/api_integration/apis/security/privileges.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index aeb9ba4c645e6..9848e1cffd32e 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -467,6 +467,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', + 'ui:infrastructure/show', ], }, logs: { @@ -480,6 +481,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', + 'ui:logs/show', ], }, uptime: { @@ -615,8 +617,10 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:infra', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', + 'ui:infrastructure/show', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', + 'ui:logs/show', 'app:uptime', 'ui:catalogue/uptime', 'ui:navLinks/uptime', @@ -693,8 +697,10 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:infra', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', + 'ui:infrastructure/show', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', + 'ui:logs/show', 'app:uptime', 'ui:catalogue/uptime', 'ui:navLinks/uptime', @@ -817,8 +823,10 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:infra', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', + 'ui:infrastructure/show', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', + 'ui:logs/show', 'app:uptime', 'ui:catalogue/uptime', 'ui:navLinks/uptime', @@ -895,8 +903,10 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:infra', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', + 'ui:infrastructure/show', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', + 'ui:logs/show', 'app:uptime', 'ui:catalogue/uptime', 'ui:navLinks/uptime', From bbfc833d03645f0913a04fbcf052105ca0078330 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 12 Feb 2019 09:58:03 -0500 Subject: [PATCH 05/34] adds feature controls security tests for infraHome --- .../infra/public/components/waffle/node.tsx | 1 + .../components/waffle/node_context_menu.tsx | 1 + .../plugins/infra/public/pages/logs/logs.tsx | 2 +- x-pack/plugins/infra/server/kibana.index.ts | 2 +- .../apis/security/privileges.ts | 15 ++ .../test/functional/apps/infra/constants.ts | 8 + .../infra/feature_controls/infra_security.ts | 207 ++++++++++++++++++ .../test/functional/apps/infra/home_page.ts | 4 +- x-pack/test/functional/apps/infra/index.ts | 1 + 9 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 x-pack/test/functional/apps/infra/constants.ts create mode 100644 x-pack/test/functional/apps/infra/feature_controls/infra_security.ts diff --git a/x-pack/plugins/infra/public/components/waffle/node.tsx b/x-pack/plugins/infra/public/components/waffle/node.tsx index 42d38a356a7fa..9d116b545d92b 100644 --- a/x-pack/plugins/infra/public/components/waffle/node.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node.tsx @@ -60,6 +60,7 @@ export class Node extends React.PureComponent { > diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index aad61aff170fb..bd8bc0b070566 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -89,6 +89,7 @@ export const NodeContextMenu = injectUICapabilities( defaultMessage: 'View logs', }), href: nodeLogsUrl, + 'data-test-subj': 'viewLogsContentMenuItem', }, ] : []), diff --git a/x-pack/plugins/infra/public/pages/logs/logs.tsx b/x-pack/plugins/infra/public/pages/logs/logs.tsx index 6a3a30a8d881f..381862e95ce25 100644 --- a/x-pack/plugins/infra/public/pages/logs/logs.tsx +++ b/x-pack/plugins/infra/public/pages/logs/logs.tsx @@ -40,7 +40,7 @@ export const LogsPage = injectI18n( const { intl } = this.props; return ( - +
{ read: { savedObject: { all: [], - read: ['config'], + read: ['config', 'infrastructure-ui-source'], }, ui: ['show'], }, diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 9848e1cffd32e..97d11245aa957 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -467,6 +467,9 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', + 'saved_object:infrastructure-ui-source/bulk_get', + 'saved_object:infrastructure-ui-source/get', + 'saved_object:infrastructure-ui-source/find', 'ui:infrastructure/show', ], }, @@ -617,6 +620,9 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:infra', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', + 'saved_object:infrastructure-ui-source/bulk_get', + 'saved_object:infrastructure-ui-source/get', + 'saved_object:infrastructure-ui-source/find', 'ui:infrastructure/show', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', @@ -697,6 +703,9 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:infra', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', + 'saved_object:infrastructure-ui-source/bulk_get', + 'saved_object:infrastructure-ui-source/get', + 'saved_object:infrastructure-ui-source/find', 'ui:infrastructure/show', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', @@ -823,6 +832,9 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:infra', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', + 'saved_object:infrastructure-ui-source/bulk_get', + 'saved_object:infrastructure-ui-source/get', + 'saved_object:infrastructure-ui-source/find', 'ui:infrastructure/show', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', @@ -903,6 +915,9 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:infra', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', + 'saved_object:infrastructure-ui-source/bulk_get', + 'saved_object:infrastructure-ui-source/get', + 'saved_object:infrastructure-ui-source/find', 'ui:infrastructure/show', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', diff --git a/x-pack/test/functional/apps/infra/constants.ts b/x-pack/test/functional/apps/infra/constants.ts new file mode 100644 index 0000000000000..42c2b4ca66782 --- /dev/null +++ b/x-pack/test/functional/apps/infra/constants.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export const DATE_WITH_DATA = new Date(1539806283000); +export const DATE_WITHOUT_DATA = new Date(1539122400000); diff --git a/x-pack/test/functional/apps/infra/feature_controls/infra_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infra_security.ts new file mode 100644 index 0000000000000..319a6856455a5 --- /dev/null +++ b/x-pack/test/functional/apps/infra/feature_controls/infra_security.ts @@ -0,0 +1,207 @@ +/* + * 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 expect from 'expect.js'; +import { KibanaFunctionalTestDefaultProviders } from 'x-pack/test/types/providers'; +import { DATE_WITH_DATA } from '../constants'; + +// tslint:disable no-default-export +export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { + const esArchiver = getService('esArchiver'); + const security = getService('security'); + const PageObjects = getPageObjects(['common', 'infraHome', 'security', 'spaceSelector']); + const find = getService('find'); + const testSubjects = getService('testSubjects'); + const appsMenu = getService('appsMenu'); + + describe('security feature controls', () => { + before(async () => { + await esArchiver.loadIfNeeded('infra/metrics_and_logs'); + }); + + describe('global infrastructure read privileges', () => { + before(async () => { + await security.role.create('global_infrastructure_read_role', { + elasticsearch: { + indices: [{ names: ['metricbeat-*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + feature: { + infrastructure: ['read'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('global_infrastructure_read_user', { + password: 'global_infrastructure_read_user-password', + roles: ['global_infrastructure_read_role'], + full_name: 'test user', + }); + + await PageObjects.security.logout(); + + await PageObjects.security.login( + 'global_infrastructure_read_user', + 'global_infrastructure_read_user-password', + { + expectSpaceSelector: false, + } + ); + }); + + after(async () => { + await Promise.all([ + security.role.delete('global_infrastructure_read_role'), + security.user.delete('global_infrastructure_read_user'), + PageObjects.security.logout(), + ]); + }); + + it('shows infrastructure navlink', async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.eql(['Infrastructure', 'Management']); + }); + + it(`landing page shows Wafflemap`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await PageObjects.infraHome.goToTime(DATE_WITH_DATA); + await testSubjects.existOrFail('waffleMap'); + }); + + it(`does not show link to view logs`, async () => { + await testSubjects.click('nodeContainer'); + await testSubjects.missingOrFail('viewLogsContentMenuItem'); + }); + }); + + describe('global infrastructure & logs read-only privileges', () => { + before(async () => { + await security.role.create('global_infrastructure_logs_read_role', { + elasticsearch: { + indices: [ + { + names: ['metricbeat-*', 'filebeat-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + }, + kibana: [ + { + feature: { + infrastructure: ['read'], + logs: ['read'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('global_infrastructure_logs_read_user', { + password: 'global_infrastructure_logs_read_user-password', + roles: ['global_infrastructure_logs_read_role'], + full_name: 'test user', + }); + + await PageObjects.security.login( + 'global_infrastructure_logs_read_user', + 'global_infrastructure_logs_read_user-password', + { + expectSpaceSelector: false, + } + ); + }); + + after(async () => { + await security.role.delete('global_infrastructure_logs_read_role'); + await security.user.delete('global_infrastructure_logs_read_user'); + }); + + it('shows infrastructure and logs navlinks', async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.eql(['Infrastructure', 'Logs', 'Management']); + }); + + it(`landing page shows Wafflemap`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await PageObjects.infraHome.goToTime(DATE_WITH_DATA); + await testSubjects.existOrFail('waffleMap'); + }); + + it(`allows user to view logs`, async () => { + await testSubjects.click('nodeContainer'); + await testSubjects.click('viewLogsContentMenuItem'); + await testSubjects.existOrFail('infraLogsPage'); + }); + }); + + describe('no infrastructure privileges', () => { + before(async () => { + await security.role.create('no_infrastructure_privileges_role', { + elasticsearch: { + indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + feature: { + discover: ['all'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('no_infrastructure_privileges_user', { + password: 'no_infrastructure_privileges_user-password', + roles: ['no_infrastructure_privileges_role'], + full_name: 'test user', + }); + + await PageObjects.security.login( + 'no_infrastructure_privileges_user', + 'no_infrastructure_privileges_user-password', + { + expectSpaceSelector: false, + } + ); + }); + + after(async () => { + await security.role.delete('no_infrastructure_privileges_role'); + await security.user.delete('no_infrastructure_privileges_user'); + }); + + const getMessageText = async () => + await (await find.byCssSelector('body>pre')).getVisibleText(); + + it(`returns a 404`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + }); + const messageText = await getMessageText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) + ); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index a36c0bac62d54..565da5e8d0e88 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -5,9 +5,7 @@ */ import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; - -const DATE_WITH_DATA = new Date(1539806283000); -const DATE_WITHOUT_DATA = new Date(1539122400000); +import { DATE_WITH_DATA, DATE_WITHOUT_DATA } from './constants'; // tslint:disable-next-line:no-default-export export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) => { diff --git a/x-pack/test/functional/apps/infra/index.ts b/x-pack/test/functional/apps/infra/index.ts index 322aea31e1263..447a91ff16619 100644 --- a/x-pack/test/functional/apps/infra/index.ts +++ b/x-pack/test/functional/apps/infra/index.ts @@ -12,5 +12,6 @@ export default ({ loadTestFile }: KibanaFunctionalTestDefaultProviders) => { this.tags('ciGroup7'); loadTestFile(require.resolve('./home_page')); + loadTestFile(require.resolve('./feature_controls/infra_security')); }); }; From e44b0eb930280a0f9cb49bae5453c45474e2358f Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 12 Feb 2019 13:59:23 -0500 Subject: [PATCH 06/34] adds infra spaces feature control tests --- .../infra/feature_controls/infra_spaces.ts | 106 ++++++++++++++++++ x-pack/test/functional/apps/infra/index.ts | 3 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/functional/apps/infra/feature_controls/infra_spaces.ts diff --git a/x-pack/test/functional/apps/infra/feature_controls/infra_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/infra_spaces.ts new file mode 100644 index 0000000000000..9e924b2c7d732 --- /dev/null +++ b/x-pack/test/functional/apps/infra/feature_controls/infra_spaces.ts @@ -0,0 +1,106 @@ +/* + * 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 expect from 'expect.js'; +import { SpacesService } from 'x-pack/test/common/services'; +import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; +import { DATE_WITH_DATA } from '../constants'; + +// tslint:disable:no-default-export +export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { + const esArchiver = getService('esArchiver'); + const spacesService: SpacesService = getService('spaces'); + const PageObjects = getPageObjects(['common', 'infraHome', 'security', 'spaceSelector']); + const find = getService('find'); + const testSubjects = getService('testSubjects'); + const appsMenu = getService('appsMenu'); + + describe('spaces feature controls', () => { + before(async () => { + await esArchiver.loadIfNeeded('infra/metrics_and_logs'); + }); + + describe('space with no features disabled', () => { + before(async () => { + // we need to load the following in every situation as deleting + // a space deletes all of the associated saved objects + await esArchiver.load('empty_kibana'); + + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: [], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + await esArchiver.unload('empty_kibana'); + }); + + it('shows Infra navlink', async () => { + await PageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.contain('Infrastructure'); + }); + + it(`landing page shows Wafflemap`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await PageObjects.infraHome.goToTime(DATE_WITH_DATA); + await testSubjects.existOrFail('waffleMap'); + }); + + it(`Shows link to view logs`, async () => { + await testSubjects.click('nodeContainer'); + await testSubjects.existOrFail('viewLogsContentMenuItem'); + }); + }); + + describe('space with Infrastructure disabled', () => { + before(async () => { + // we need to load the following in every situation as deleting + // a space deletes all of the associated saved objects + await esArchiver.load('empty_kibana'); + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: ['infrastructure'], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + await esArchiver.unload('empty_kibana'); + }); + + it(`doesn't show infrastructure navlink`, async () => { + await PageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).not.to.contain('Infrastructure'); + }); + + it(`Loading app returns log viewer`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', '', { + basePath: '/s/custom_space', + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + }); + + await testSubjects.existOrFail('infraLogsPage'); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/infra/index.ts b/x-pack/test/functional/apps/infra/index.ts index 447a91ff16619..2f65d12fe8c9f 100644 --- a/x-pack/test/functional/apps/infra/index.ts +++ b/x-pack/test/functional/apps/infra/index.ts @@ -8,10 +8,11 @@ import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; // tslint:disable-next-line:no-default-export export default ({ loadTestFile }: KibanaFunctionalTestDefaultProviders) => { - describe('InfraOps app', function() { + describe.only('InfraOps app', function() { this.tags('ciGroup7'); loadTestFile(require.resolve('./home_page')); loadTestFile(require.resolve('./feature_controls/infra_security')); + loadTestFile(require.resolve('./feature_controls/infra_spaces')); }); }; From f5db5c825078bc7dd9442dd5477cf552494a4eab Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 12 Feb 2019 14:00:58 -0500 Subject: [PATCH 07/34] remove debug code --- x-pack/test/functional/apps/infra/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/infra/index.ts b/x-pack/test/functional/apps/infra/index.ts index 2f65d12fe8c9f..99dc34177329c 100644 --- a/x-pack/test/functional/apps/infra/index.ts +++ b/x-pack/test/functional/apps/infra/index.ts @@ -8,7 +8,7 @@ import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; // tslint:disable-next-line:no-default-export export default ({ loadTestFile }: KibanaFunctionalTestDefaultProviders) => { - describe.only('InfraOps app', function() { + describe('InfraOps app', function() { this.tags('ciGroup7'); loadTestFile(require.resolve('./home_page')); From a1c3345879637d898282289209d2e07d3b49a59c Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Fri, 15 Feb 2019 10:49:30 -0500 Subject: [PATCH 08/34] a sample readonly implementation, ignoring 'logs' privileges --- .../ui/public/capabilities/react/index.ts | 0 .../react/inject_ui_capabilities.test.tsx | 0 .../react/inject_ui_capabilities.tsx | 0 .../react/requires_ui_capability.test.tsx | 0 .../react/requires_ui_capability.tsx | 0 .../react/ui_capabilities_provider.tsx | 0 .../fields_configuration_panel.tsx | 32 +- .../indices_configuration_panel.tsx | 11 +- .../name_configuration_panel.tsx | 9 +- .../source_configuration_flyout.tsx | 279 ++++++++++-------- x-pack/plugins/infra/server/kibana.index.ts | 10 +- .../apis/security/privileges.ts | 33 +++ 12 files changed, 231 insertions(+), 143 deletions(-) rename src/{ => legacy}/ui/public/capabilities/react/index.ts (100%) rename src/{ => legacy}/ui/public/capabilities/react/inject_ui_capabilities.test.tsx (100%) rename src/{ => legacy}/ui/public/capabilities/react/inject_ui_capabilities.tsx (100%) rename src/{ => legacy}/ui/public/capabilities/react/requires_ui_capability.test.tsx (100%) rename src/{ => legacy}/ui/public/capabilities/react/requires_ui_capability.tsx (100%) rename src/{ => legacy}/ui/public/capabilities/react/ui_capabilities_provider.tsx (100%) diff --git a/src/ui/public/capabilities/react/index.ts b/src/legacy/ui/public/capabilities/react/index.ts similarity index 100% rename from src/ui/public/capabilities/react/index.ts rename to src/legacy/ui/public/capabilities/react/index.ts diff --git a/src/ui/public/capabilities/react/inject_ui_capabilities.test.tsx b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx similarity index 100% rename from src/ui/public/capabilities/react/inject_ui_capabilities.test.tsx rename to src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx diff --git a/src/ui/public/capabilities/react/inject_ui_capabilities.tsx b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx similarity index 100% rename from src/ui/public/capabilities/react/inject_ui_capabilities.tsx rename to src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx diff --git a/src/ui/public/capabilities/react/requires_ui_capability.test.tsx b/src/legacy/ui/public/capabilities/react/requires_ui_capability.test.tsx similarity index 100% rename from src/ui/public/capabilities/react/requires_ui_capability.test.tsx rename to src/legacy/ui/public/capabilities/react/requires_ui_capability.test.tsx diff --git a/src/ui/public/capabilities/react/requires_ui_capability.tsx b/src/legacy/ui/public/capabilities/react/requires_ui_capability.tsx similarity index 100% rename from src/ui/public/capabilities/react/requires_ui_capability.tsx rename to src/legacy/ui/public/capabilities/react/requires_ui_capability.tsx diff --git a/src/ui/public/capabilities/react/ui_capabilities_provider.tsx b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx similarity index 100% rename from src/ui/public/capabilities/react/ui_capabilities_provider.tsx rename to src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx diff --git a/x-pack/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx b/x-pack/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx index 6311ad5cda9a3..fae3372777473 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx @@ -14,6 +14,7 @@ interface FieldsConfigurationPanelProps { containerFieldProps: InputFieldProps; hostFieldProps: InputFieldProps; isLoading: boolean; + disabled: boolean; podFieldProps: InputFieldProps; tiebreakerFieldProps: InputFieldProps; timestampFieldProps: InputFieldProps; @@ -23,6 +24,7 @@ export const FieldsConfigurationPanel = ({ containerFieldProps, hostFieldProps, isLoading, + disabled, podFieldProps, tiebreakerFieldProps, timestampFieldProps, @@ -57,7 +59,12 @@ export const FieldsConfigurationPanel = ({ /> } > - + @@ -106,7 +113,12 @@ export const FieldsConfigurationPanel = ({ /> } > - + } > - + } > - + ); diff --git a/x-pack/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx b/x-pack/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx index e941c293e2a3a..f1da43ca2c1a8 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx @@ -12,12 +12,14 @@ import { InputFieldProps } from './source_configuration_form_state'; interface IndicesConfigurationPanelProps { isLoading: boolean; + disabled: boolean; logAliasFieldProps: InputFieldProps; metricAliasFieldProps: InputFieldProps; } export const IndicesConfigurationPanel = ({ isLoading, + disabled, logAliasFieldProps, metricAliasFieldProps, }: IndicesConfigurationPanelProps) => ( @@ -53,7 +55,7 @@ export const IndicesConfigurationPanel = ({ > @@ -78,7 +80,12 @@ export const IndicesConfigurationPanel = ({ /> } > - + ); diff --git a/x-pack/plugins/infra/public/components/source_configuration/name_configuration_panel.tsx b/x-pack/plugins/infra/public/components/source_configuration/name_configuration_panel.tsx index 664cfbc155333..beb19ec14d2f6 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/name_configuration_panel.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/name_configuration_panel.tsx @@ -12,11 +12,13 @@ import { InputFieldProps } from './source_configuration_form_state'; interface NameConfigurationPanelProps { isLoading: boolean; + disabled: boolean; nameFieldProps: InputFieldProps; } export const NameConfigurationPanel = ({ isLoading, + disabled, nameFieldProps, }: NameConfigurationPanelProps) => ( @@ -37,7 +39,12 @@ export const NameConfigurationPanel = ({ } > - + ); diff --git a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx index bb1e6d5697a38..8da58dc43dc55 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx @@ -19,6 +19,8 @@ import { import React from 'react'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { UICapabilities } from 'ui/capabilities'; +import { injectUICapabilities } from 'ui/capabilities/react'; import { WithSource } from '../../containers/with_source'; import { FieldsConfigurationPanel } from './fields_configuration_panel'; import { IndicesConfigurationPanel } from './indices_configuration_panel'; @@ -28,142 +30,159 @@ import { WithSourceConfigurationFormState } from './source_configuration_form_st const noop = () => undefined; +const isDisabled = (uiCapabilities: UICapabilities) => !uiCapabilities.infrastructure.save; + interface SourceConfigurationFlyoutProps { intl: InjectedIntl; + uiCapabilities: UICapabilities; } -export const SourceConfigurationFlyout = injectI18n(({ intl }: SourceConfigurationFlyoutProps) => ( - - {({ disable: close, value: isVisible }) => - isVisible ? ( - - {({ create, configuration, exists, isLoading, update }) => - configuration ? ( - - {({ - getCurrentFormState, - getNameFieldProps, - getLogAliasFieldProps, - getMetricAliasFieldProps, - getFieldFieldProps, - isFormValid, - resetForm, - updates, - }) => ( - - - -

- -

-
-
- - - - - - - - - - - {updates.length === 0 ? ( - close()} - > - - - ) : ( - { - resetForm(); - close(); - }} - > +export const SourceConfigurationFlyout = injectUICapabilities( + injectI18n(({ intl, uiCapabilities }: SourceConfigurationFlyoutProps) => ( + + {({ disable: close, value: isVisible }) => + isVisible ? ( + + {({ create, configuration, exists, isLoading, update }) => + configuration ? ( + + {({ + getCurrentFormState, + getNameFieldProps, + getLogAliasFieldProps, + getMetricAliasFieldProps, + getFieldFieldProps, + isFormValid, + resetForm, + updates, + }) => ( + + + +

+ {isDisabled(uiCapabilities) ? ( - - )} - - - - {isLoading ? ( - - Loading - - ) : ( - - (exists ? update(updates) : create(getCurrentFormState())).then( - () => resetForm() - ) - } - > + ) : ( - + )} +

+
+
+ + + + + + + + + + + {updates.length === 0 ? ( + close()} + > + + + ) : ( + { + resetForm(); + close(); + }} + > + + + )} + + + {!isDisabled(uiCapabilities) && ( + + {isLoading ? ( + + Loading + + ) : ( + + (exists ? update(updates) : create(getCurrentFormState())).then( + () => resetForm() + ) + } + > + + + )} + )} - - - -
- )} -
- ) : null - } -
- ) : null - } -
-)); +
+
+
+ )} +
+ ) : null + } +
+ ) : null + } +
+ )) +); diff --git a/x-pack/plugins/infra/server/kibana.index.ts b/x-pack/plugins/infra/server/kibana.index.ts index 66d635eab82a8..18d6b31d3d9d4 100644 --- a/x-pack/plugins/infra/server/kibana.index.ts +++ b/x-pack/plugins/infra/server/kibana.index.ts @@ -35,10 +35,10 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { privileges: { all: { savedObject: { - all: [], + all: ['infrastructure-ui-source'], read: ['config'], }, - ui: [], + ui: ['show', 'save'], }, read: { savedObject: { @@ -62,15 +62,15 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { privileges: { all: { savedObject: { - all: [], + all: ['infrastructure-ui-source'], read: ['config'], }, - ui: [], + ui: ['show', 'save'], }, read: { savedObject: { all: [], - read: ['config'], + read: ['config', 'infrastructure-ui-source'], }, ui: ['show'], }, diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index ad3de96bfb9ee..98957298ec8a5 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -479,9 +479,18 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:kibana', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', + 'saved_object:infrastructure-ui-source/bulk_get', + 'saved_object:infrastructure-ui-source/get', + 'saved_object:infrastructure-ui-source/find', + 'saved_object:infrastructure-ui-source/create', + 'saved_object:infrastructure-ui-source/bulk_create', + 'saved_object:infrastructure-ui-source/update', + 'saved_object:infrastructure-ui-source/delete', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', + 'ui:infrastructure/show', + 'ui:infrastructure/save', ], read: [ 'login:', @@ -507,9 +516,18 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:kibana', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', + 'saved_object:infrastructure-ui-source/bulk_get', + 'saved_object:infrastructure-ui-source/get', + 'saved_object:infrastructure-ui-source/find', + 'saved_object:infrastructure-ui-source/create', + 'saved_object:infrastructure-ui-source/bulk_create', + 'saved_object:infrastructure-ui-source/update', + 'saved_object:infrastructure-ui-source/delete', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', + 'ui:logs/show', + 'ui:logs/save', ], read: [ 'login:', @@ -521,6 +539,9 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', + 'saved_object:infrastructure-ui-source/bulk_get', + 'saved_object:infrastructure-ui-source/get', + 'saved_object:infrastructure-ui-source/find', 'ui:logs/show', ], }, @@ -672,10 +693,16 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:infrastructure-ui-source/bulk_get', 'saved_object:infrastructure-ui-source/get', 'saved_object:infrastructure-ui-source/find', + 'saved_object:infrastructure-ui-source/create', + 'saved_object:infrastructure-ui-source/bulk_create', + 'saved_object:infrastructure-ui-source/update', + 'saved_object:infrastructure-ui-source/delete', 'ui:infrastructure/show', + 'ui:infrastructure/save', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', 'ui:logs/show', + 'ui:logs/save', 'app:uptime', 'ui:catalogue/uptime', 'ui:navLinks/uptime', @@ -886,10 +913,16 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:infrastructure-ui-source/bulk_get', 'saved_object:infrastructure-ui-source/get', 'saved_object:infrastructure-ui-source/find', + 'saved_object:infrastructure-ui-source/create', + 'saved_object:infrastructure-ui-source/bulk_create', + 'saved_object:infrastructure-ui-source/update', + 'saved_object:infrastructure-ui-source/delete', 'ui:infrastructure/show', + 'ui:infrastructure/save', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', 'ui:logs/show', + 'ui:logs/save', 'app:uptime', 'ui:catalogue/uptime', 'ui:navLinks/uptime', From 0cb1bf0a31512b588eab9d0d4241a65cc9238fb1 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Fri, 15 Feb 2019 11:02:31 -0500 Subject: [PATCH 09/34] ts fixes --- .../infra/feature_controls/infra_spaces.ts | 1 - .../security_only/tests/infra.ts | 45 +++++------------- .../security_only/tests/logs.ts | 47 ++++++------------- 3 files changed, 27 insertions(+), 66 deletions(-) diff --git a/x-pack/test/functional/apps/infra/feature_controls/infra_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/infra_spaces.ts index 9e924b2c7d732..d10cb6de417e6 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infra_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infra_spaces.ts @@ -13,7 +13,6 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa const esArchiver = getService('esArchiver'); const spacesService: SpacesService = getService('spaces'); const PageObjects = getPageObjects(['common', 'infraHome', 'security', 'spaceSelector']); - const find = getService('find'); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); diff --git a/x-pack/test/ui_capabilities/security_only/tests/infra.ts b/x-pack/test/ui_capabilities/security_only/tests/infra.ts index 6591752efe5fd..3dca355bf7d4a 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/infra.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/infra.ts @@ -30,48 +30,22 @@ export default function advancedSettingsTests({ case 'superuser': case 'all': case 'dual_privileges_all': - case 'infrastructure_read': // TODO: awaiting read/write privilege for infra + case 'infrastructure_all': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: true, + save: true, }); break; - // these users have a read-only view of Advanced Settings case 'dual_privileges_read': + case 'infrastructure_read': + // these users have a read-only view of Infrastructure expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: true, - }); - break; - // these users can't do anything with Advanced Settings - case 'advancedSettings_all': - case 'advancedSettings_read': - case 'apm_all': - case 'canvas_all': - case 'canvas_read': - case 'dev_tools_read': - case 'dashboard_all': - case 'dashboard_read': - case 'discover_all': - case 'discover_read': - case 'graph_all': - case 'graph_read': - case 'maps_all': - case 'maps_read': - case 'logs_read': - case 'ml_all': - case 'monitoring_all': - case 'timelion_all': - case 'timelion_read': - case 'uptime_read': - case 'visualize_all': - case 'visualize_read': - expect(uiCapabilities.success).to.be(true); - expect(uiCapabilities.value).to.have.property('infrastructure'); - expect(uiCapabilities.value!.infrastructure).to.eql({ - show: false, + save: false, }); break; case 'legacy_all': @@ -79,8 +53,15 @@ export default function advancedSettingsTests({ expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; + // all other users can't do anything with Infrastructure default: - throw new UnreachableError(scenario); + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('infrastructure'); + expect(uiCapabilities.value!.infrastructure).to.eql({ + show: false, + save: false, + }); + break; } }); }); diff --git a/x-pack/test/ui_capabilities/security_only/tests/logs.ts b/x-pack/test/ui_capabilities/security_only/tests/logs.ts index 2f37a4181f208..d19f0284ef307 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/logs.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/logs.ts @@ -26,52 +26,26 @@ export default function advancedSettingsTests({ password: scenario.password, }); switch (scenario.username) { - // these users have a read/write view of Infrastructure + // these users have a read/write view of Logs case 'superuser': case 'all': case 'dual_privileges_all': - case 'logs_read': // TODO: awaiting read/write privilege for logs + case 'logs_all': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: true, + save: true, }); break; - // these users have a read-only view of Advanced Settings + // these users have a read-only view of Logs case 'dual_privileges_read': + case 'logs_read': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: true, - }); - break; - // these users can't do anything with Advanced Settings - case 'advancedSettings_all': - case 'advancedSettings_read': - case 'apm_all': - case 'canvas_all': - case 'canvas_read': - case 'dev_tools_read': - case 'dashboard_all': - case 'dashboard_read': - case 'discover_all': - case 'discover_read': - case 'graph_all': - case 'graph_read': - case 'infrastructure_read': - case 'maps_all': - case 'maps_read': - case 'ml_all': - case 'monitoring_all': - case 'timelion_all': - case 'timelion_read': - case 'uptime_read': - case 'visualize_all': - case 'visualize_read': - expect(uiCapabilities.success).to.be(true); - expect(uiCapabilities.value).to.have.property('logs'); - expect(uiCapabilities.value!.logs).to.eql({ - show: false, + save: false, }); break; case 'legacy_all': @@ -79,8 +53,15 @@ export default function advancedSettingsTests({ expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; + // all other users can't do anything with Logs default: - throw new UnreachableError(scenario); + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('logs'); + expect(uiCapabilities.value!.logs).to.eql({ + show: false, + save: false, + }); + break; } }); }); From dc61811c978357d60a0e0b626773a309759b9080 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Fri, 15 Feb 2019 13:09:05 -0500 Subject: [PATCH 10/34] fix capability expectations --- x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts | 3 +++ x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts | 3 +++ x-pack/test/ui_capabilities/spaces_only/tests/infra.ts | 2 ++ x-pack/test/ui_capabilities/spaces_only/tests/logs.ts | 2 ++ 4 files changed, 10 insertions(+) diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts index 8e414e4b7bd38..f8c91545e8a34 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts @@ -35,6 +35,7 @@ export default function infrastructureTests({ getService }: KibanaFunctionalTest expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: true, + save: true, }); break; // these users have a read only view of Infra @@ -45,6 +46,7 @@ export default function infrastructureTests({ getService }: KibanaFunctionalTest expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: true, + save: false, }); break; // the nothing_space has no features enabled, so even if we have @@ -60,6 +62,7 @@ export default function infrastructureTests({ getService }: KibanaFunctionalTest expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: false, + save: false, }); break; // if we don't have access at the space itself, we're diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts index 384f303e05e42..c1826e0174581 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts @@ -35,6 +35,7 @@ export default function logsTests({ getService }: KibanaFunctionalTestDefaultPro expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: true, + save: true, }); break; // these users have a read only view of Logs @@ -45,6 +46,7 @@ export default function logsTests({ getService }: KibanaFunctionalTestDefaultPro expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: true, + save: false, }); break; // the nothing_space has no features enabled, so even if we have @@ -60,6 +62,7 @@ export default function logsTests({ getService }: KibanaFunctionalTestDefaultPro expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: false, + save: false, }); break; // if we don't have access at the space itself, we're diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/infra.ts b/x-pack/test/ui_capabilities/spaces_only/tests/infra.ts index 01e95aa1e1516..12957bb238b5d 100644 --- a/x-pack/test/ui_capabilities/spaces_only/tests/infra.ts +++ b/x-pack/test/ui_capabilities/spaces_only/tests/infra.ts @@ -39,6 +39,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: true, + save: true, }); break; case 'nothing_space': @@ -47,6 +48,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: false, + save: false, }); break; default: diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/logs.ts b/x-pack/test/ui_capabilities/spaces_only/tests/logs.ts index 04283ee296f4d..9dd18a20d8170 100644 --- a/x-pack/test/ui_capabilities/spaces_only/tests/logs.ts +++ b/x-pack/test/ui_capabilities/spaces_only/tests/logs.ts @@ -39,6 +39,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: true, + save: true, }); break; case 'nothing_space': @@ -47,6 +48,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: false, + save: false, }); break; default: From 7981ad450a7b1e43de60790fb197e43c60e5466b Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 21 Feb 2019 10:47:05 -0800 Subject: [PATCH 11/34] Removing RequiresUICapability component, since there are no usages --- .../ui/public/capabilities/react/index.ts | 1 - .../react/requires_ui_capability.test.tsx | 132 ------------------ .../react/requires_ui_capability.tsx | 34 ----- 3 files changed, 167 deletions(-) delete mode 100644 src/legacy/ui/public/capabilities/react/requires_ui_capability.test.tsx delete mode 100644 src/legacy/ui/public/capabilities/react/requires_ui_capability.tsx diff --git a/src/legacy/ui/public/capabilities/react/index.ts b/src/legacy/ui/public/capabilities/react/index.ts index 4310b10a25b13..78d95a2603290 100644 --- a/src/legacy/ui/public/capabilities/react/index.ts +++ b/src/legacy/ui/public/capabilities/react/index.ts @@ -19,4 +19,3 @@ export { UICapabilitiesProvider } from './ui_capabilities_provider'; export { injectUICapabilities } from './inject_ui_capabilities'; -export { RequiresUICapability } from './requires_ui_capability'; diff --git a/src/legacy/ui/public/capabilities/react/requires_ui_capability.test.tsx b/src/legacy/ui/public/capabilities/react/requires_ui_capability.test.tsx deleted file mode 100644 index f9cc7f8a55910..0000000000000 --- a/src/legacy/ui/public/capabilities/react/requires_ui_capability.test.tsx +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -jest.mock('ui/chrome', () => ({ - getInjected(key: string) { - if (key === 'uiCapabilities') { - return { - app: { - feature1: true, - feature2: false, - }, - }; - } - }, -})); - -import { mount } from 'enzyme'; -import React from 'react'; -import { UICapabilitiesProvider } from '.'; -import { RequiresUICapability } from './requires_ui_capability'; - -describe('', () => { - it('renders the child if the UI Capability is satisfied', () => { - const wrapper = mount( - - -
this renders
-
-
- ); - - expect(wrapper).toMatchInlineSnapshot(` - - - -
- this renders -
-
-
-
-`); - }); - - it('does not render the child if the UI Capability is not satisfied', () => { - const wrapper = mount( - - -
this does not render
-
-
- ); - - expect(wrapper).toMatchInlineSnapshot(` - - - - - -`); - }); - - it('does not render the child if the UI Capability is not defined', () => { - const wrapper = mount( - - -
this does not render
-
-
- ); - - expect(wrapper).toMatchInlineSnapshot(` - - - - - -`); - }); -}); diff --git a/src/legacy/ui/public/capabilities/react/requires_ui_capability.tsx b/src/legacy/ui/public/capabilities/react/requires_ui_capability.tsx deleted file mode 100644 index 6fa45ad3dd1c6..0000000000000 --- a/src/legacy/ui/public/capabilities/react/requires_ui_capability.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import _ from 'lodash'; -import { UICapabilities } from '../ui_capabilities'; -import { injectUICapabilities } from './inject_ui_capabilities'; - -interface Props { - uiCapability: string; - uiCapabilities: UICapabilities; - children: React.ReactElement; -} - -export const RequiresUICapability = injectUICapabilities((props: Props) => { - if (_.get(props.uiCapabilities, props.uiCapability)) { - return props.children; - } - return null; -}); From d3bd237acd8d5235db52b37f77ed5244fd85107b Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 21 Feb 2019 13:52:48 -0800 Subject: [PATCH 12/34] Driving the source configuration seperately for logs/infrastructure --- .../ui/public/capabilities/react/index.ts | 1 + x-pack/plugins/apm/index.js | 2 +- .../source_configuration_flyout.tsx | 30 +-- .../components/waffle/node_context_menu.tsx | 24 +- .../plugins/infra/public/pages/home/index.tsx | 189 +++++++------- .../plugins/infra/public/pages/logs/logs.tsx | 240 +++++++++--------- x-pack/plugins/infra/server/kibana.index.ts | 4 +- 7 files changed, 253 insertions(+), 237 deletions(-) diff --git a/src/legacy/ui/public/capabilities/react/index.ts b/src/legacy/ui/public/capabilities/react/index.ts index 78d95a2603290..b25edf68264c6 100644 --- a/src/legacy/ui/public/capabilities/react/index.ts +++ b/src/legacy/ui/public/capabilities/react/index.ts @@ -19,3 +19,4 @@ export { UICapabilitiesProvider } from './ui_capabilities_provider'; export { injectUICapabilities } from './inject_ui_capabilities'; +export { UICapabilities } from '../index'; diff --git a/x-pack/plugins/apm/index.js b/x-pack/plugins/apm/index.js index fe815ac6dbbf5..4a3e9a6f9f9e2 100644 --- a/x-pack/plugins/apm/index.js +++ b/x-pack/plugins/apm/index.js @@ -86,7 +86,7 @@ export function apm(kibana) { all: [], read: ['config'] }, - ui: [] + ui: ['show'] } }, privilegesTooltip: i18n.translate('xpack.apm.privileges.tooltip', { diff --git a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx index 8da58dc43dc55..6febf949c83a4 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx @@ -19,8 +19,6 @@ import { import React from 'react'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import { UICapabilities } from 'ui/capabilities'; -import { injectUICapabilities } from 'ui/capabilities/react'; import { WithSource } from '../../containers/with_source'; import { FieldsConfigurationPanel } from './fields_configuration_panel'; import { IndicesConfigurationPanel } from './indices_configuration_panel'; @@ -30,15 +28,13 @@ import { WithSourceConfigurationFormState } from './source_configuration_form_st const noop = () => undefined; -const isDisabled = (uiCapabilities: UICapabilities) => !uiCapabilities.infrastructure.save; - interface SourceConfigurationFlyoutProps { intl: InjectedIntl; - uiCapabilities: UICapabilities; + shouldAllowEdit: boolean; } -export const SourceConfigurationFlyout = injectUICapabilities( - injectI18n(({ intl, uiCapabilities }: SourceConfigurationFlyoutProps) => ( +export const SourceConfigurationFlyout = injectI18n( + ({ intl, shouldAllowEdit }: SourceConfigurationFlyoutProps) => ( {({ disable: close, value: isVisible }) => isVisible ? ( @@ -78,15 +74,15 @@ export const SourceConfigurationFlyout = injectUICapabilities(

- {isDisabled(uiCapabilities) ? ( + {shouldAllowEdit ? ( ) : ( )}

@@ -95,13 +91,13 @@ export const SourceConfigurationFlyout = injectUICapabilities( @@ -110,7 +106,7 @@ export const SourceConfigurationFlyout = injectUICapabilities( containerFieldProps={getFieldFieldProps('container')} hostFieldProps={getFieldFieldProps('host')} isLoading={isLoading} - disabled={isDisabled(uiCapabilities)} + disabled={!shouldAllowEdit} podFieldProps={getFieldFieldProps('pod')} tiebreakerFieldProps={getFieldFieldProps('tiebreaker')} timestampFieldProps={getFieldFieldProps('timestamp')} @@ -148,7 +144,7 @@ export const SourceConfigurationFlyout = injectUICapabilities( )} - {!isDisabled(uiCapabilities) && ( + {!!shouldAllowEdit && ( {isLoading ? ( @@ -184,5 +180,5 @@ export const SourceConfigurationFlyout = injectUICapabilities( ) : null }
- )) + ) ); diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index bd8bc0b070566..3deebc9b5ef77 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -65,16 +65,18 @@ export const NodeContextMenu = injectUICapabilities( }) : undefined; - const apmTracesUrl = { - name: intl.formatMessage( - { - id: 'xpack.infra.nodeContextMenu.viewAPMTraces', - defaultMessage: 'View {nodeType} APM traces', - }, - { nodeType } - ), - href: `../app/apm#/traces?_g=()&kuery=${APM_FIELDS[nodeType]}~20~3A~20~22${node.id}~22`, - }; + const apmTracesUrl = uiCapabilities.apm.show + ? { + name: intl.formatMessage( + { + id: 'xpack.infra.nodeContextMenu.viewAPMTraces', + defaultMessage: 'View {nodeType} APM traces', + }, + { nodeType } + ), + href: `../app/apm#/traces?_g=()&kuery=${APM_FIELDS[nodeType]}~20~3A~20~22${node.id}~22`, + } + : undefined; const panels: EuiContextMenuPanelDescriptor[] = [ { @@ -104,7 +106,7 @@ export const NodeContextMenu = injectUICapabilities( }, ] : []), - ...[apmTracesUrl], + ...(apmTracesUrl ? [apmTracesUrl] : []), ], }, ]; diff --git a/x-pack/plugins/infra/public/pages/home/index.tsx b/x-pack/plugins/infra/public/pages/home/index.tsx index b1df9a14c36ae..a876315aa40b7 100644 --- a/x-pack/plugins/infra/public/pages/home/index.tsx +++ b/x-pack/plugins/infra/public/pages/home/index.tsx @@ -7,6 +7,7 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React from 'react'; +import { injectUICapabilities, UICapabilities } from 'ui/capabilities/react'; import { HomePageContent } from './page_content'; import { HomeToolbar } from './toolbar'; @@ -25,99 +26,107 @@ import { SourceErrorPage, SourceLoadingPage, WithSource } from '../../containers interface HomePageProps { intl: InjectedIntl; + uiCapabilities: UICapabilities; } -export const HomePage = injectI18n( - class extends React.Component { - public static displayName = 'HomePage'; +export const HomePage = injectUICapabilities( + injectI18n( + class extends React.Component { + public static displayName = 'HomePage'; - public render() { - const { intl } = this.props; + public render() { + const { intl, uiCapabilities } = this.props; - return ( - -
- - - {({ - derivedIndexPattern, - hasFailed, - isLoading, - lastFailureMessage, - load, - metricIndicesExist, - }) => - isLoading ? ( - - ) : metricIndicesExist ? ( - <> - - - - - - - ) : hasFailed ? ( - - ) : ( - - {({ basePath }) => ( - - - - {intl.formatMessage({ - id: 'xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel', - defaultMessage: 'View setup instructions', - })} - - - - - {({ enable }) => ( - - {intl.formatMessage({ - id: 'xpack.infra.configureSourceActionLabel', - defaultMessage: 'Change source configuration', - })} - - )} - - - - } - data-test-subj="noMetricsIndicesPrompt" - /> - )} - - ) - } - - - ); + return ( + +
+ + + {({ + derivedIndexPattern, + hasFailed, + isLoading, + lastFailureMessage, + load, + metricIndicesExist, + }) => + isLoading ? ( + + ) : metricIndicesExist ? ( + <> + + + + + + + ) : hasFailed ? ( + + ) : ( + + {({ basePath }) => ( + + + + {intl.formatMessage({ + id: + 'xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel', + defaultMessage: 'View setup instructions', + })} + + + {uiCapabilities.infrastructure.configureSource ? ( + + + {({ enable }) => ( + + {intl.formatMessage({ + id: 'xpack.infra.configureSourceActionLabel', + defaultMessage: 'Change source configuration', + })} + + )} + + + ) : null} + + } + data-test-subj="noMetricsIndicesPrompt" + /> + )} + + ) + } + + + ); + } } - } + ) ); diff --git a/x-pack/plugins/infra/public/pages/logs/logs.tsx b/x-pack/plugins/infra/public/pages/logs/logs.tsx index 381862e95ce25..0cbbfbd82898b 100644 --- a/x-pack/plugins/infra/public/pages/logs/logs.tsx +++ b/x-pack/plugins/infra/public/pages/logs/logs.tsx @@ -7,6 +7,7 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React from 'react'; +import { injectUICapabilities, UICapabilities } from 'ui/capabilities/react'; import { LogsPageContent } from './page_content'; import { LogsToolbar } from './toolbar'; @@ -30,126 +31,133 @@ import { SourceErrorPage, SourceLoadingPage, WithSource } from '../../containers interface Props { intl: InjectedIntl; + uiCapabilities: UICapabilities; } -export const LogsPage = injectI18n( - class extends React.Component { - public static displayName = 'LogsPage'; +export const LogsPage = injectUICapabilities( + injectI18n( + class extends React.Component { + public static displayName = 'LogsPage'; - public render() { - const { intl } = this.props; + public render() { + const { intl, uiCapabilities } = this.props; - return ( - -
- - {({ - derivedIndexPattern, - hasFailed, - isLoading, - lastFailureMessage, - load, - logIndicesExist, - sourceId, - }) => ( - <> - - {isLoading ? ( - - ) : logIndicesExist ? ( - <> - - - - - - - - {({ applyFilterQueryFromKueryExpression }) => ( - - - {({ showFlyout, setFlyoutItem }) => ( - - )} - - - {({ flyoutItem, hideFlyout, loading }) => ( - - )} - - + return ( + +
+ + {({ + derivedIndexPattern, + hasFailed, + isLoading, + lastFailureMessage, + load, + logIndicesExist, + sourceId, + }) => ( + <> + + {isLoading ? ( + + ) : logIndicesExist ? ( + <> + + + + + + + + {({ applyFilterQueryFromKueryExpression }) => ( + + + {({ showFlyout, setFlyoutItem }) => ( + + )} + + + {({ flyoutItem, hideFlyout, loading }) => ( + + )} + + + )} + + + ) : hasFailed ? ( + + ) : ( + + {({ basePath }) => ( + + + + {intl.formatMessage({ + id: + 'xpack.infra.logsPage.noLoggingIndicesInstructionsActionLabel', + defaultMessage: 'View setup instructions', + })} + + + {uiCapabilities.logs.configureSource ? ( + + + {({ enable }) => ( + + {intl.formatMessage({ + id: 'xpack.infra.configureSourceActionLabel', + defaultMessage: 'Change source configuration', + })} + + )} + + + ) : null} + + } + /> )} - - - ) : hasFailed ? ( - - ) : ( - - {({ basePath }) => ( - - - - {intl.formatMessage({ - id: - 'xpack.infra.logsPage.noLoggingIndicesInstructionsActionLabel', - defaultMessage: 'View setup instructions', - })} - - - - - {({ enable }) => ( - - {intl.formatMessage({ - id: 'xpack.infra.configureSourceActionLabel', - defaultMessage: 'Change source configuration', - })} - - )} - - - - } - /> - )} - - )} - - )} - - - ); + + )} + + )} + + + ); + } } - } + ) ); diff --git a/x-pack/plugins/infra/server/kibana.index.ts b/x-pack/plugins/infra/server/kibana.index.ts index 18d6b31d3d9d4..99a52a753b374 100644 --- a/x-pack/plugins/infra/server/kibana.index.ts +++ b/x-pack/plugins/infra/server/kibana.index.ts @@ -38,7 +38,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { all: ['infrastructure-ui-source'], read: ['config'], }, - ui: ['show', 'save'], + ui: ['show', 'configureSource'], }, read: { savedObject: { @@ -65,7 +65,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { all: ['infrastructure-ui-source'], read: ['config'], }, - ui: ['show', 'save'], + ui: ['show', 'configureSource'], }, read: { savedObject: { From 19bfb58ce730f49f88f3b07abc21533db463b6af Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 21 Feb 2019 16:05:29 -0800 Subject: [PATCH 13/34] Adding infrastructure feature controls security functional tests --- .../apm/public/components/app/Main/index.js | 2 +- .../components/waffle/node_context_menu.tsx | 3 +- x-pack/plugins/infra/public/pages/404.tsx | 2 +- .../plugins/infra/public/pages/home/index.tsx | 7 +- .../infra/public/pages/metrics/index.tsx | 2 +- x-pack/plugins/infra/public/routes.tsx | 4 +- .../apps/infra/feature_controls/index.ts | 14 + .../infra/feature_controls/infra_security.ts | 207 --------- .../infrastructure_security.ts | 418 ++++++++++++++++++ ...fra_spaces.ts => infrastructure_spaces.ts} | 2 +- x-pack/test/functional/apps/infra/index.ts | 3 +- 11 files changed, 448 insertions(+), 216 deletions(-) create mode 100644 x-pack/test/functional/apps/infra/feature_controls/index.ts delete mode 100644 x-pack/test/functional/apps/infra/feature_controls/infra_security.ts create mode 100644 x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts rename x-pack/test/functional/apps/infra/feature_controls/{infra_spaces.ts => infrastructure_spaces.ts} (98%) diff --git a/x-pack/plugins/apm/public/components/app/Main/index.js b/x-pack/plugins/apm/public/components/app/Main/index.js index cbf1925e5650a..2a3f69faad2fd 100644 --- a/x-pack/plugins/apm/public/components/app/Main/index.js +++ b/x-pack/plugins/apm/public/components/app/Main/index.js @@ -21,7 +21,7 @@ const MainContainer = styled.div` export default function Main() { return ( - + diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index 3deebc9b5ef77..4f46b7aa845f5 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -75,6 +75,7 @@ export const NodeContextMenu = injectUICapabilities( { nodeType } ), href: `../app/apm#/traces?_g=()&kuery=${APM_FIELDS[nodeType]}~20~3A~20~22${node.id}~22`, + 'data-test-subj': 'viewApmTracesContextMenuItem', } : undefined; @@ -91,7 +92,7 @@ export const NodeContextMenu = injectUICapabilities( defaultMessage: 'View logs', }), href: nodeLogsUrl, - 'data-test-subj': 'viewLogsContentMenuItem', + 'data-test-subj': 'viewLogsContextMenuItem', }, ] : []), diff --git a/x-pack/plugins/infra/public/pages/404.tsx b/x-pack/plugins/infra/public/pages/404.tsx index d99d7339c79d7..631c6d8299e08 100644 --- a/x-pack/plugins/infra/public/pages/404.tsx +++ b/x-pack/plugins/infra/public/pages/404.tsx @@ -10,7 +10,7 @@ import React from 'react'; export class NotFoundPage extends React.PureComponent { public render() { return ( -
+
{intl.formatMessage({ id: @@ -105,7 +106,11 @@ export const HomePage = injectUICapabilities( {({ enable }) => ( - + {intl.formatMessage({ id: 'xpack.infra.configureSourceActionLabel', defaultMessage: 'Change source configuration', diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index d775d5e5d236f..a41e94fa8833c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -114,7 +114,7 @@ export const MetricDetail = withTheme(
- + = ({ history, uiCapabilities } {uiCapabilities.logs.show && } {uiCapabilities.infrastructure.show && } - + {uiCapabilities.infrastructure.show && ( + + )} diff --git a/x-pack/test/functional/apps/infra/feature_controls/index.ts b/x-pack/test/functional/apps/infra/feature_controls/index.ts new file mode 100644 index 0000000000000..a58edd72391f6 --- /dev/null +++ b/x-pack/test/functional/apps/infra/feature_controls/index.ts @@ -0,0 +1,14 @@ +/* + * 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 { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { + describe('feature controls', () => { + loadTestFile(require.resolve('./infrastructure_security')); + loadTestFile(require.resolve('./infrastructure_spaces')); + }); +} diff --git a/x-pack/test/functional/apps/infra/feature_controls/infra_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infra_security.ts deleted file mode 100644 index 319a6856455a5..0000000000000 --- a/x-pack/test/functional/apps/infra/feature_controls/infra_security.ts +++ /dev/null @@ -1,207 +0,0 @@ -/* - * 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 expect from 'expect.js'; -import { KibanaFunctionalTestDefaultProviders } from 'x-pack/test/types/providers'; -import { DATE_WITH_DATA } from '../constants'; - -// tslint:disable no-default-export -export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { - const esArchiver = getService('esArchiver'); - const security = getService('security'); - const PageObjects = getPageObjects(['common', 'infraHome', 'security', 'spaceSelector']); - const find = getService('find'); - const testSubjects = getService('testSubjects'); - const appsMenu = getService('appsMenu'); - - describe('security feature controls', () => { - before(async () => { - await esArchiver.loadIfNeeded('infra/metrics_and_logs'); - }); - - describe('global infrastructure read privileges', () => { - before(async () => { - await security.role.create('global_infrastructure_read_role', { - elasticsearch: { - indices: [{ names: ['metricbeat-*'], privileges: ['read', 'view_index_metadata'] }], - }, - kibana: [ - { - feature: { - infrastructure: ['read'], - }, - spaces: ['*'], - }, - ], - }); - - await security.user.create('global_infrastructure_read_user', { - password: 'global_infrastructure_read_user-password', - roles: ['global_infrastructure_read_role'], - full_name: 'test user', - }); - - await PageObjects.security.logout(); - - await PageObjects.security.login( - 'global_infrastructure_read_user', - 'global_infrastructure_read_user-password', - { - expectSpaceSelector: false, - } - ); - }); - - after(async () => { - await Promise.all([ - security.role.delete('global_infrastructure_read_role'), - security.user.delete('global_infrastructure_read_user'), - PageObjects.security.logout(), - ]); - }); - - it('shows infrastructure navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); - expect(navLinks).to.eql(['Infrastructure', 'Management']); - }); - - it(`landing page shows Wafflemap`, async () => { - await PageObjects.common.navigateToActualUrl('infraOps', 'home', { - ensureCurrentUrl: true, - shouldLoginIfPrompted: false, - }); - await PageObjects.infraHome.goToTime(DATE_WITH_DATA); - await testSubjects.existOrFail('waffleMap'); - }); - - it(`does not show link to view logs`, async () => { - await testSubjects.click('nodeContainer'); - await testSubjects.missingOrFail('viewLogsContentMenuItem'); - }); - }); - - describe('global infrastructure & logs read-only privileges', () => { - before(async () => { - await security.role.create('global_infrastructure_logs_read_role', { - elasticsearch: { - indices: [ - { - names: ['metricbeat-*', 'filebeat-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - }, - kibana: [ - { - feature: { - infrastructure: ['read'], - logs: ['read'], - }, - spaces: ['*'], - }, - ], - }); - - await security.user.create('global_infrastructure_logs_read_user', { - password: 'global_infrastructure_logs_read_user-password', - roles: ['global_infrastructure_logs_read_role'], - full_name: 'test user', - }); - - await PageObjects.security.login( - 'global_infrastructure_logs_read_user', - 'global_infrastructure_logs_read_user-password', - { - expectSpaceSelector: false, - } - ); - }); - - after(async () => { - await security.role.delete('global_infrastructure_logs_read_role'); - await security.user.delete('global_infrastructure_logs_read_user'); - }); - - it('shows infrastructure and logs navlinks', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); - expect(navLinks).to.eql(['Infrastructure', 'Logs', 'Management']); - }); - - it(`landing page shows Wafflemap`, async () => { - await PageObjects.common.navigateToActualUrl('infraOps', 'home', { - ensureCurrentUrl: true, - shouldLoginIfPrompted: false, - }); - await PageObjects.infraHome.goToTime(DATE_WITH_DATA); - await testSubjects.existOrFail('waffleMap'); - }); - - it(`allows user to view logs`, async () => { - await testSubjects.click('nodeContainer'); - await testSubjects.click('viewLogsContentMenuItem'); - await testSubjects.existOrFail('infraLogsPage'); - }); - }); - - describe('no infrastructure privileges', () => { - before(async () => { - await security.role.create('no_infrastructure_privileges_role', { - elasticsearch: { - indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }], - }, - kibana: [ - { - feature: { - discover: ['all'], - }, - spaces: ['*'], - }, - ], - }); - - await security.user.create('no_infrastructure_privileges_user', { - password: 'no_infrastructure_privileges_user-password', - roles: ['no_infrastructure_privileges_role'], - full_name: 'test user', - }); - - await PageObjects.security.login( - 'no_infrastructure_privileges_user', - 'no_infrastructure_privileges_user-password', - { - expectSpaceSelector: false, - } - ); - }); - - after(async () => { - await security.role.delete('no_infrastructure_privileges_role'); - await security.user.delete('no_infrastructure_privileges_user'); - }); - - const getMessageText = async () => - await (await find.byCssSelector('body>pre')).getVisibleText(); - - it(`returns a 404`, async () => { - await PageObjects.common.navigateToActualUrl('infraOps', '', { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - }); - const messageText = await getMessageText(); - expect(messageText).to.eql( - JSON.stringify({ - statusCode: 404, - error: 'Not Found', - message: 'Not Found', - }) - ); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts new file mode 100644 index 0000000000000..19c153ef49819 --- /dev/null +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts @@ -0,0 +1,418 @@ +/* + * 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 expect from 'expect.js'; +import { KibanaFunctionalTestDefaultProviders } from 'x-pack/test/types/providers'; +import { DATE_WITH_DATA } from '../constants'; + +// tslint:disable no-default-export +export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { + const esArchiver = getService('esArchiver'); + const security = getService('security'); + const PageObjects = getPageObjects(['common', 'infraHome', 'security', 'error']); + const testSubjects = getService('testSubjects'); + const appsMenu = getService('appsMenu'); + + describe('infrastructure security', () => { + describe('global infrastructure all privileges', () => { + before(async () => { + await security.role.create('global_infrastructure_all_role', { + elasticsearch: { + indices: [{ names: ['metricbeat-*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + feature: { + infrastructure: ['all'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('global_infrastructure_all_user', { + password: 'global_infrastructure_all_user-password', + roles: ['global_infrastructure_all_role'], + full_name: 'test user', + }); + + await PageObjects.security.logout(); + + await PageObjects.security.login( + 'global_infrastructure_all_user', + 'global_infrastructure_all_user-password', + { + expectSpaceSelector: false, + } + ); + }); + + after(async () => { + await Promise.all([ + security.role.delete('global_infrastructure_all_role'), + security.user.delete('global_infrastructure_all_user'), + PageObjects.security.logout(), + ]); + }); + + it('shows infrastructure navlink', async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.eql(['Infrastructure', 'Management']); + }); + + describe('infrastructure landing page without data', () => { + it(`shows 'Change source configuration' button`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await testSubjects.existOrFail('infraViewSetupInstructionsButton'); + await testSubjects.existOrFail('infraChangeSourceConfigurationButton'); + }); + }); + + describe('infrastructure landing page with data', () => { + before(async () => { + await esArchiver.load('infra/metrics_and_logs'); + }); + + after(async () => { + await esArchiver.unload('infra/metrics_and_logs'); + }); + + it(`shows Wafflemap`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await PageObjects.infraHome.goToTime(DATE_WITH_DATA); + await testSubjects.existOrFail('waffleMap'); + }); + + describe('context menu', () => { + before(async () => { + await testSubjects.click('nodeContainer'); + }); + + it(`does not show link to view logs`, async () => { + await testSubjects.missingOrFail('viewLogsContextMenuItem'); + }); + + it(`does not show link to view apm traces`, async () => { + await testSubjects.missingOrFail('viewApmTracesContextMenuItem'); + }); + }); + }); + + it(`metrics page is visible`, async () => { + await PageObjects.common.navigateToActualUrl( + 'infraOps', + '/metrics/host/demo-stack-redis-01', + { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + } + ); + await testSubjects.existOrFail('infraMetricsPage'); + }); + }); + + describe('global infrastructure read privileges', () => { + before(async () => { + await security.role.create('global_infrastructure_read_role', { + elasticsearch: { + indices: [{ names: ['metricbeat-*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + feature: { + infrastructure: ['read'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('global_infrastructure_read_user', { + password: 'global_infrastructure_read_user-password', + roles: ['global_infrastructure_read_role'], + full_name: 'test user', + }); + + await PageObjects.security.logout(); + + await PageObjects.security.login( + 'global_infrastructure_read_user', + 'global_infrastructure_read_user-password', + { + expectSpaceSelector: false, + } + ); + }); + + after(async () => { + await Promise.all([ + security.role.delete('global_infrastructure_read_role'), + security.user.delete('global_infrastructure_read_user'), + PageObjects.security.logout(), + ]); + }); + + it('shows infrastructure navlink', async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.eql(['Infrastructure', 'Management']); + }); + + describe('infrastructure landing page without data', () => { + it(`doesn't show 'Change source configuration' button`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await testSubjects.existOrFail('infraViewSetupInstructionsButton'); + await testSubjects.missingOrFail('infraChangeSourceConfigurationButton'); + }); + }); + + describe('infrastructure landing page with data', () => { + before(async () => { + await esArchiver.load('infra/metrics_and_logs'); + }); + + after(async () => { + await esArchiver.unload('infra/metrics_and_logs'); + }); + + it(`shows Wafflemap`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await PageObjects.infraHome.goToTime(DATE_WITH_DATA); + await testSubjects.existOrFail('waffleMap'); + }); + + describe('context menu', () => { + before(async () => { + await testSubjects.click('nodeContainer'); + }); + + it(`does not show link to view logs`, async () => { + await testSubjects.missingOrFail('viewLogsContextMenuItem'); + }); + + it(`does not show link to view apm traces`, async () => { + await testSubjects.missingOrFail('viewApmTracesContextMenuItem'); + }); + }); + }); + + it(`metrics page is visible`, async () => { + await PageObjects.common.navigateToActualUrl( + 'infraOps', + '/metrics/host/demo-stack-redis-01', + { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + } + ); + await testSubjects.existOrFail('infraMetricsPage'); + }); + }); + + describe('global infrastructure read & logs read privileges', () => { + before(async () => { + await security.role.create('global_infrastructure_logs_read_role', { + elasticsearch: { + indices: [ + { + names: ['metricbeat-*', 'filebeat-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + }, + kibana: [ + { + feature: { + infrastructure: ['read'], + logs: ['read'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('global_infrastructure_logs_read_user', { + password: 'global_infrastructure_logs_read_user-password', + roles: ['global_infrastructure_logs_read_role'], + full_name: 'test user', + }); + + await PageObjects.security.login( + 'global_infrastructure_logs_read_user', + 'global_infrastructure_logs_read_user-password', + { + expectSpaceSelector: false, + } + ); + }); + + after(async () => { + await security.role.delete('global_infrastructure_logs_read_role'); + await security.user.delete('global_infrastructure_logs_read_user'); + }); + + describe('infrastructure landing page with data', () => { + before(async () => { + await esArchiver.load('infra/metrics_and_logs'); + }); + + after(async () => { + await esArchiver.unload('infra/metrics_and_logs'); + }); + + it(`context menu allows user to view logs`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await PageObjects.infraHome.goToTime(DATE_WITH_DATA); + await testSubjects.existOrFail('waffleMap'); + await testSubjects.click('nodeContainer'); + await testSubjects.click('viewLogsContextMenuItem'); + await testSubjects.existOrFail('infraLogsPage'); + }); + }); + }); + + describe('global infrastructure read & apm privileges', () => { + before(async () => { + await security.role.create('global_infrastructure_apm_read_role', { + elasticsearch: { + indices: [ + { + names: ['metricbeat-*', 'filebeat-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + }, + kibana: [ + { + feature: { + infrastructure: ['read'], + apm: ['all'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('global_infrastructure_apm_read_user', { + password: 'global_infrastructure_apm_read_user-password', + roles: ['global_infrastructure_apm_read_role'], + full_name: 'test user', + }); + + await PageObjects.security.login( + 'global_infrastructure_apm_read_user', + 'global_infrastructure_apm_read_user-password', + { + expectSpaceSelector: false, + } + ); + }); + + after(async () => { + await security.role.delete('global_infrastructure_apm_read_role'); + await security.user.delete('global_infrastructure_apm_read_user'); + }); + + describe('infrastructure landing page with data', () => { + before(async () => { + await esArchiver.load('infra/metrics_and_logs'); + }); + + after(async () => { + await esArchiver.unload('infra/metrics_and_logs'); + }); + + it(`context menu allows user to view APM traces`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await PageObjects.infraHome.goToTime(DATE_WITH_DATA); + await testSubjects.existOrFail('waffleMap'); + await testSubjects.click('nodeContainer'); + await testSubjects.click('viewApmTracesContextMenuItem'); + await testSubjects.existOrFail('apmMainContainer'); + }); + }); + }); + + // this is taking place of the usual "no privileges" test by ensuring that if the user + // has all logs privileges, it doesn't grant them access to the infrastructure app + describe('global logs all privileges', () => { + before(async () => { + await security.role.create('no_infrastructure_privileges_role', { + elasticsearch: { + indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + feature: { + logs: ['all'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('no_infrastructure_privileges_user', { + password: 'no_infrastructure_privileges_user-password', + roles: ['no_infrastructure_privileges_role'], + full_name: 'test user', + }); + + await PageObjects.security.login( + 'no_infrastructure_privileges_user', + 'no_infrastructure_privileges_user-password', + { + expectSpaceSelector: false, + } + ); + }); + + after(async () => { + await security.role.delete('no_infrastructure_privileges_role'); + await security.user.delete('no_infrastructure_privileges_user'); + }); + + it(`infrastructure landing page renders not found page`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + }); + await testSubjects.existOrFail('infraNotFoundPage'); + }); + + it(`metrics page renders not found page`, async () => { + await PageObjects.common.navigateToActualUrl( + 'infraOps', + '/metrics/host/demo-stack-redis-01', + { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + } + ); + await testSubjects.existOrFail('infraNotFoundPage'); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/infra/feature_controls/infra_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts similarity index 98% rename from x-pack/test/functional/apps/infra/feature_controls/infra_spaces.ts rename to x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts index d10cb6de417e6..717895992d925 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infra_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts @@ -60,7 +60,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa it(`Shows link to view logs`, async () => { await testSubjects.click('nodeContainer'); - await testSubjects.existOrFail('viewLogsContentMenuItem'); + await testSubjects.existOrFail('viewLogsContextMenuItem'); }); }); diff --git a/x-pack/test/functional/apps/infra/index.ts b/x-pack/test/functional/apps/infra/index.ts index 99dc34177329c..505becaddd159 100644 --- a/x-pack/test/functional/apps/infra/index.ts +++ b/x-pack/test/functional/apps/infra/index.ts @@ -12,7 +12,6 @@ export default ({ loadTestFile }: KibanaFunctionalTestDefaultProviders) => { this.tags('ciGroup7'); loadTestFile(require.resolve('./home_page')); - loadTestFile(require.resolve('./feature_controls/infra_security')); - loadTestFile(require.resolve('./feature_controls/infra_spaces')); + loadTestFile(require.resolve('./feature_controls')); }); }; From 8eab19ae82a42c3691c85dfe91715097ca647310 Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 21 Feb 2019 16:30:17 -0800 Subject: [PATCH 14/34] Adding spaces infrastructure tests --- .../feature_controls/infrastructure_spaces.ts | 121 ++++++++++++++++-- 1 file changed, 111 insertions(+), 10 deletions(-) diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts index 717895992d925..194334f48fcd8 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts @@ -16,7 +16,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); - describe('spaces feature controls', () => { + describe('infrastructure spaces', () => { before(async () => { await esArchiver.loadIfNeeded('infra/metrics_and_logs'); }); @@ -51,16 +51,25 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa it(`landing page shows Wafflemap`, async () => { await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + basePath: '/s/custom_space', ensureCurrentUrl: true, - shouldLoginIfPrompted: false, }); await PageObjects.infraHome.goToTime(DATE_WITH_DATA); await testSubjects.existOrFail('waffleMap'); }); - it(`Shows link to view logs`, async () => { - await testSubjects.click('nodeContainer'); - await testSubjects.existOrFail('viewLogsContextMenuItem'); + describe('context menu', () => { + before(async () => { + await testSubjects.click('nodeContainer'); + }); + + it(`shows link to view logs`, async () => { + await testSubjects.existOrFail('viewLogsContextMenuItem'); + }); + + it(`shows link to view apm traces`, async () => { + await testSubjects.existOrFail('viewApmTracesContextMenuItem'); + }); }); }); @@ -91,14 +100,106 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa expect(navLinks).not.to.contain('Infrastructure'); }); - it(`Loading app returns log viewer`, async () => { - await PageObjects.common.navigateToActualUrl('infraOps', '', { + it(`infrastructure landing page renders not found page`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + basePath: '/s/custom_space', + ensureCurrentUrl: true, + }); + await testSubjects.existOrFail('infraNotFoundPage'); + }); + + it(`metrics page renders not found page`, async () => { + await PageObjects.common.navigateToActualUrl( + 'infraOps', + '/metrics/host/demo-stack-redis-01', + { + basePath: '/s/custom_space', + ensureCurrentUrl: true, + } + ); + await testSubjects.existOrFail('infraNotFoundPage'); + }); + }); + + describe('space with Logs disabled', () => { + before(async () => { + // we need to load the following in every situation as deleting + // a space deletes all of the associated saved objects + await esArchiver.load('empty_kibana'); + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: ['logs'], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + await esArchiver.unload('empty_kibana'); + }); + + it(`landing page shows Wafflemap`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { + basePath: '/s/custom_space', + ensureCurrentUrl: true, + }); + await PageObjects.infraHome.goToTime(DATE_WITH_DATA); + await testSubjects.existOrFail('waffleMap'); + }); + + describe('context menu', () => { + before(async () => { + await testSubjects.click('nodeContainer'); + }); + + it(`doesn't show link to view logs`, async () => { + await testSubjects.missingOrFail('viewLogsContextMenuItem'); + }); + + it(`shows link to view apm traces`, async () => { + await testSubjects.existOrFail('viewApmTracesContextMenuItem'); + }); + }); + }); + + describe('space with APM disabled', () => { + before(async () => { + // we need to load the following in every situation as deleting + // a space deletes all of the associated saved objects + await esArchiver.load('empty_kibana'); + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: ['apm'], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + await esArchiver.unload('empty_kibana'); + }); + + it(`landing page shows Wafflemap`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'home', { basePath: '/s/custom_space', - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, + ensureCurrentUrl: true, }); + await PageObjects.infraHome.goToTime(DATE_WITH_DATA); + await testSubjects.existOrFail('waffleMap'); + }); - await testSubjects.existOrFail('infraLogsPage'); + describe('context menu', () => { + before(async () => { + await testSubjects.click('nodeContainer'); + }); + + it(`shows link to view logs`, async () => { + await testSubjects.existOrFail('viewLogsContextMenuItem'); + }); + + it(`doesn't show link to view apm traces`, async () => { + await testSubjects.missingOrFail('viewApmTracesContextMenuItem'); + }); }); }); }); From b20b6b65a4615ddc026bcde21a0758f186d3742e Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 22 Feb 2019 11:25:54 -0800 Subject: [PATCH 15/34] Adding logs functional tests --- .../plugins/infra/public/pages/home/index.tsx | 4 +- .../plugins/infra/public/pages/logs/logs.tsx | 7 +- .../apps/infra/feature_controls/index.ts | 2 + .../infrastructure_security.ts | 21 +- .../feature_controls/infrastructure_spaces.ts | 2 +- .../infra/feature_controls/logs_security.ts | 193 ++++++++++++++++++ .../infra/feature_controls/logs_spaces.ts | 98 +++++++++ 7 files changed, 315 insertions(+), 12 deletions(-) create mode 100644 x-pack/test/functional/apps/infra/feature_controls/logs_security.ts create mode 100644 x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts diff --git a/x-pack/plugins/infra/public/pages/home/index.tsx b/x-pack/plugins/infra/public/pages/home/index.tsx index e64232a162ec2..ff2560adb1699 100644 --- a/x-pack/plugins/infra/public/pages/home/index.tsx +++ b/x-pack/plugins/infra/public/pages/home/index.tsx @@ -93,7 +93,7 @@ export const HomePage = injectUICapabilities( href={`${basePath}/app/kibana#/home/tutorial_directory/metrics`} color="primary" fill - data-test-subj="infraViewSetupInstructionsButton" + data-test-subj="infrastructureViewSetupInstructionsButton" > {intl.formatMessage({ id: @@ -109,7 +109,7 @@ export const HomePage = injectUICapabilities( {intl.formatMessage({ id: 'xpack.infra.configureSourceActionLabel', diff --git a/x-pack/plugins/infra/public/pages/logs/logs.tsx b/x-pack/plugins/infra/public/pages/logs/logs.tsx index 0cbbfbd82898b..c2e8f700d7c54 100644 --- a/x-pack/plugins/infra/public/pages/logs/logs.tsx +++ b/x-pack/plugins/infra/public/pages/logs/logs.tsx @@ -124,6 +124,7 @@ export const LogsPage = injectUICapabilities( href={`${basePath}/app/kibana#/home/tutorial_directory/logging`} color="primary" fill + data-test-subj="logsViewSetupInstructionsButton" > {intl.formatMessage({ id: @@ -136,7 +137,11 @@ export const LogsPage = injectUICapabilities( {({ enable }) => ( - + {intl.formatMessage({ id: 'xpack.infra.configureSourceActionLabel', defaultMessage: 'Change source configuration', diff --git a/x-pack/test/functional/apps/infra/feature_controls/index.ts b/x-pack/test/functional/apps/infra/feature_controls/index.ts index a58edd72391f6..4a649f48156b8 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/index.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/index.ts @@ -10,5 +10,7 @@ export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) describe('feature controls', () => { loadTestFile(require.resolve('./infrastructure_security')); loadTestFile(require.resolve('./infrastructure_spaces')); + loadTestFile(require.resolve('./logs_security')); + loadTestFile(require.resolve('./logs_spaces')); }); } diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts index 19c153ef49819..040cd5bd0eda8 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts @@ -11,7 +11,7 @@ import { DATE_WITH_DATA } from '../constants'; export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { const esArchiver = getService('esArchiver'); const security = getService('security'); - const PageObjects = getPageObjects(['common', 'infraHome', 'security', 'error']); + const PageObjects = getPageObjects(['common', 'infraHome', 'security']); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); @@ -70,8 +70,8 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa ensureCurrentUrl: true, shouldLoginIfPrompted: false, }); - await testSubjects.existOrFail('infraViewSetupInstructionsButton'); - await testSubjects.existOrFail('infraChangeSourceConfigurationButton'); + await testSubjects.existOrFail('infrastructureViewSetupInstructionsButton'); + await testSubjects.existOrFail('infrastructureChangeSourceConfigurationButton'); }); }); @@ -175,8 +175,8 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa ensureCurrentUrl: true, shouldLoginIfPrompted: false, }); - await testSubjects.existOrFail('infraViewSetupInstructionsButton'); - await testSubjects.missingOrFail('infraChangeSourceConfigurationButton'); + await testSubjects.existOrFail('infrastructureViewSetupInstructionsButton'); + await testSubjects.missingOrFail('infrastructureChangeSourceConfigurationButton'); }); }); @@ -356,9 +356,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa }); }); - // this is taking place of the usual "no privileges" test by ensuring that if the user - // has all logs privileges, it doesn't grant them access to the infrastructure app - describe('global logs all privileges', () => { + describe('global infrastructure no privileges', () => { before(async () => { await security.role.create('no_infrastructure_privileges_role', { elasticsearch: { @@ -394,6 +392,13 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await security.user.delete('no_infrastructure_privileges_user'); }); + it(`doesn't show infrastructure navlink`, async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.not.contain(['Infrastructure']); + }); + it(`infrastructure landing page renders not found page`, async () => { await PageObjects.common.navigateToActualUrl('infraOps', 'home', { ensureCurrentUrl: false, diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts index 194334f48fcd8..30d4fb30b3488 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts @@ -39,7 +39,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await esArchiver.unload('empty_kibana'); }); - it('shows Infra navlink', async () => { + it('shows Infrastructure navlink', async () => { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts new file mode 100644 index 0000000000000..8f4976a597129 --- /dev/null +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts @@ -0,0 +1,193 @@ +/* + * 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 expect from 'expect.js'; +import { KibanaFunctionalTestDefaultProviders } from 'x-pack/test/types/providers'; + +// tslint:disable no-default-export +export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { + const security = getService('security'); + const PageObjects = getPageObjects(['common', 'infraHome', 'security']); + const testSubjects = getService('testSubjects'); + const appsMenu = getService('appsMenu'); + + describe('logs security', () => { + describe('global logs all privileges', () => { + before(async () => { + await security.role.create('global_logs_all_role', { + elasticsearch: { + indices: [{ names: ['metricbeat-*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + feature: { + logs: ['all'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('global_logs_all_user', { + password: 'global_logs_all_user-password', + roles: ['global_logs_all_role'], + full_name: 'test user', + }); + + await PageObjects.security.logout(); + + await PageObjects.security.login('global_logs_all_user', 'global_logs_all_user-password', { + expectSpaceSelector: false, + }); + }); + + after(async () => { + await Promise.all([ + security.role.delete('global_logs_all_role'), + security.user.delete('global_logs_all_user'), + PageObjects.security.logout(), + ]); + }); + + it('shows logs navlink', async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.eql(['Logs', 'Management']); + }); + + describe('logs landing page without data', () => { + it(`shows 'Change source configuration' button`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'logs', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await testSubjects.existOrFail('infraLogsPage'); + await testSubjects.existOrFail('logsViewSetupInstructionsButton'); + await testSubjects.existOrFail('logsChangeSourceConfigurationButton'); + }); + }); + }); + + describe('global logs read privileges', () => { + before(async () => { + await security.role.create('global_logs_read_role', { + elasticsearch: { + indices: [{ names: ['metricbeat-*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + feature: { + logs: ['read'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('global_logs_read_user', { + password: 'global_logs_read_user-password', + roles: ['global_logs_read_role'], + full_name: 'test user', + }); + + await PageObjects.security.logout(); + + await PageObjects.security.login( + 'global_logs_read_user', + 'global_logs_read_user-password', + { + expectSpaceSelector: false, + } + ); + }); + + after(async () => { + await Promise.all([ + security.role.delete('global_logs_read_role'), + security.user.delete('global_logs_read_user'), + PageObjects.security.logout(), + ]); + }); + + it('shows logs navlink', async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.eql(['Logs', 'Management']); + }); + + describe('logs landing page without data', () => { + it(`doesn't show 'Change source configuration' button`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'logs', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await testSubjects.existOrFail('infraLogsPage'); + await testSubjects.existOrFail('logsViewSetupInstructionsButton'); + await testSubjects.missingOrFail('logsChangeSourceConfigurationButton'); + }); + }); + }); + + describe('global logs no privileges', () => { + before(async () => { + await security.role.create('global_logs_no_privileges_role', { + elasticsearch: { + indices: [{ names: ['metricbeat-*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + feature: { + infrastructure: ['all'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('global_logs_no_privileges_user', { + password: 'global_logs_no_privileges_user-password', + roles: ['global_logs_no_privileges_role'], + full_name: 'test user', + }); + + await PageObjects.security.logout(); + + await PageObjects.security.login( + 'global_logs_no_privileges_user', + 'global_logs_no_privileges_user-password', + { + expectSpaceSelector: false, + } + ); + }); + + after(async () => { + await Promise.all([ + security.role.delete('global_logs_no_privileges_role'), + security.user.delete('global_logs_no_privileges_user'), + PageObjects.security.logout(), + ]); + }); + + it(`doesn't show logs navlink`, async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.not.contain('Logs'); + }); + + it('logs landing page renders not found page', async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'logs', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await testSubjects.existOrFail('infraNotFoundPage'); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts new file mode 100644 index 0000000000000..1c51bb4161b1d --- /dev/null +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts @@ -0,0 +1,98 @@ +/* + * 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 expect from 'expect.js'; +import { SpacesService } from 'x-pack/test/common/services'; +import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { + const esArchiver = getService('esArchiver'); + const spacesService: SpacesService = getService('spaces'); + const PageObjects = getPageObjects(['common', 'infraHome', 'security', 'spaceSelector']); + const testSubjects = getService('testSubjects'); + const appsMenu = getService('appsMenu'); + + describe('logs spaces', () => { + describe('space with no features disabled', () => { + before(async () => { + // we need to load the following in every situation as deleting + // a space deletes all of the associated saved objects + await esArchiver.load('empty_kibana'); + + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: [], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + await esArchiver.unload('empty_kibana'); + }); + + it('shows Logs navlink', async () => { + await PageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.contain('Logs'); + }); + + describe('logs landing page without data', () => { + it(`shows 'Change source configuration' button`, async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'logs', { + basePath: '/s/custom_space', + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await testSubjects.existOrFail('infraLogsPage'); + await testSubjects.existOrFail('logsViewSetupInstructionsButton'); + await testSubjects.existOrFail('logsChangeSourceConfigurationButton'); + }); + }); + }); + + describe('space with Logs disabled', () => { + before(async () => { + // we need to load the following in every situation as deleting + // a space deletes all of the associated saved objects + await esArchiver.load('empty_kibana'); + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: ['logs'], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + await esArchiver.unload('empty_kibana'); + }); + + it(`doesn't show Logs navlink`, async () => { + await PageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.not.contain('Logs'); + }); + + it('logs landing page renders not found page', async () => { + await PageObjects.common.navigateToActualUrl('infraOps', 'logs', { + basePath: '/s/custom_space', + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await testSubjects.existOrFail('infraNotFoundPage'); + }); + }); + }); +} From d9dcccfa17fd0416ae6f72bc1f94b81235e370b1 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 22 Feb 2019 12:49:03 -0800 Subject: [PATCH 16/34] Reworking the ui capability tests to be more consistent --- .../ui_capabilities/security_and_spaces/tests/index.ts | 2 +- .../tests/{infra.ts => infrastructure.ts} | 8 ++++---- .../ui_capabilities/security_and_spaces/tests/logs.ts | 8 ++++---- x-pack/test/ui_capabilities/security_only/tests/index.ts | 2 +- .../security_only/tests/{infra.ts => infrastructure.ts} | 6 +++--- x-pack/test/ui_capabilities/security_only/tests/logs.ts | 6 +++--- x-pack/test/ui_capabilities/spaces_only/tests/index.ts | 2 +- .../spaces_only/tests/{infra.ts => infrastructure.ts} | 4 ++-- x-pack/test/ui_capabilities/spaces_only/tests/logs.ts | 4 ++-- 9 files changed, 21 insertions(+), 21 deletions(-) rename x-pack/test/ui_capabilities/security_and_spaces/tests/{infra.ts => infrastructure.ts} (96%) rename x-pack/test/ui_capabilities/security_only/tests/{infra.ts => infrastructure.ts} (95%) rename x-pack/test/ui_capabilities/spaces_only/tests/{infra.ts => infrastructure.ts} (96%) diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts index 749c6107d85c4..2f98c6a286fe9 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts @@ -60,7 +60,7 @@ export default function uiCapabilitesTests({ loadTestFile(require.resolve('./dev_tools')); loadTestFile(require.resolve('./discover')); loadTestFile(require.resolve('./graph')); - loadTestFile(require.resolve('./infra')); + loadTestFile(require.resolve('./infrastructure')); loadTestFile(require.resolve('./logs')); loadTestFile(require.resolve('./maps')); loadTestFile(require.resolve('./nav_links')); diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/infrastructure.ts similarity index 96% rename from x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts rename to x-pack/test/ui_capabilities/security_and_spaces/tests/infrastructure.ts index f8c91545e8a34..b76f62ef0ccc1 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/infra.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/infrastructure.ts @@ -16,7 +16,7 @@ import { UserAtSpaceScenarios } from '../scenarios'; export default function infrastructureTests({ getService }: KibanaFunctionalTestDefaultProviders) { const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities'); - describe('infrastructure-ui', () => { + describe('infrastructure', () => { UserAtSpaceScenarios.forEach(scenario => { it(`${scenario.id}`, async () => { const { user, space } = scenario; @@ -35,7 +35,7 @@ export default function infrastructureTests({ getService }: KibanaFunctionalTest expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: true, - save: true, + configureSource: true, }); break; // these users have a read only view of Infra @@ -46,7 +46,7 @@ export default function infrastructureTests({ getService }: KibanaFunctionalTest expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: true, - save: false, + configureSource: false, }); break; // the nothing_space has no features enabled, so even if we have @@ -62,7 +62,7 @@ export default function infrastructureTests({ getService }: KibanaFunctionalTest expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: false, - save: false, + configureSource: false, }); break; // if we don't have access at the space itself, we're diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts index c1826e0174581..d52baf75122b3 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/logs.ts @@ -16,7 +16,7 @@ import { UserAtSpaceScenarios } from '../scenarios'; export default function logsTests({ getService }: KibanaFunctionalTestDefaultProviders) { const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities'); - describe('logs-ui', () => { + describe('logs', () => { UserAtSpaceScenarios.forEach(scenario => { it(`${scenario.id}`, async () => { const { user, space } = scenario; @@ -35,7 +35,7 @@ export default function logsTests({ getService }: KibanaFunctionalTestDefaultPro expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: true, - save: true, + configureSource: true, }); break; // these users have a read only view of Logs @@ -46,7 +46,7 @@ export default function logsTests({ getService }: KibanaFunctionalTestDefaultPro expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: true, - save: false, + configureSource: false, }); break; // the nothing_space has no features enabled, so even if we have @@ -62,7 +62,7 @@ export default function logsTests({ getService }: KibanaFunctionalTestDefaultPro expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: false, - save: false, + configureSource: false, }); break; // if we don't have access at the space itself, we're diff --git a/x-pack/test/ui_capabilities/security_only/tests/index.ts b/x-pack/test/ui_capabilities/security_only/tests/index.ts index 918c219aa0455..d519ed1090636 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/index.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/index.ts @@ -49,7 +49,7 @@ export default function uiCapabilitesTests({ loadTestFile(require.resolve('./dev_tools')); loadTestFile(require.resolve('./discover')); loadTestFile(require.resolve('./graph')); - loadTestFile(require.resolve('./infra')); + loadTestFile(require.resolve('./infrastructure')); loadTestFile(require.resolve('./logs')); loadTestFile(require.resolve('./maps')); loadTestFile(require.resolve('./nav_links')); diff --git a/x-pack/test/ui_capabilities/security_only/tests/infra.ts b/x-pack/test/ui_capabilities/security_only/tests/infrastructure.ts similarity index 95% rename from x-pack/test/ui_capabilities/security_only/tests/infra.ts rename to x-pack/test/ui_capabilities/security_only/tests/infrastructure.ts index 3dca355bf7d4a..7259eb58d23cc 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/infra.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/infrastructure.ts @@ -35,7 +35,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: true, - save: true, + configureSource: true, }); break; case 'dual_privileges_read': @@ -45,7 +45,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: true, - save: false, + configureSource: false, }); break; case 'legacy_all': @@ -59,7 +59,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: false, - save: false, + configureSource: false, }); break; } diff --git a/x-pack/test/ui_capabilities/security_only/tests/logs.ts b/x-pack/test/ui_capabilities/security_only/tests/logs.ts index d19f0284ef307..b0126bd2a43aa 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/logs.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/logs.ts @@ -35,7 +35,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: true, - save: true, + configureSource: true, }); break; // these users have a read-only view of Logs @@ -45,7 +45,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: true, - save: false, + configureSource: false, }); break; case 'legacy_all': @@ -59,7 +59,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: false, - save: false, + configureSource: false, }); break; } diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/index.ts b/x-pack/test/ui_capabilities/spaces_only/tests/index.ts index 7ac99da695cb6..5f21900d2d938 100644 --- a/x-pack/test/ui_capabilities/spaces_only/tests/index.ts +++ b/x-pack/test/ui_capabilities/spaces_only/tests/index.ts @@ -36,7 +36,7 @@ export default function uiCapabilitesTests({ loadTestFile(require.resolve('./dev_tools')); loadTestFile(require.resolve('./discover')); loadTestFile(require.resolve('./graph')); - loadTestFile(require.resolve('./infra')); + loadTestFile(require.resolve('./infrastructure')); loadTestFile(require.resolve('./logs')); loadTestFile(require.resolve('./maps')); loadTestFile(require.resolve('./nav_links')); diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/infra.ts b/x-pack/test/ui_capabilities/spaces_only/tests/infrastructure.ts similarity index 96% rename from x-pack/test/ui_capabilities/spaces_only/tests/infra.ts rename to x-pack/test/ui_capabilities/spaces_only/tests/infrastructure.ts index 12957bb238b5d..7c83b43c5ebc4 100644 --- a/x-pack/test/ui_capabilities/spaces_only/tests/infra.ts +++ b/x-pack/test/ui_capabilities/spaces_only/tests/infrastructure.ts @@ -39,7 +39,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: true, - save: true, + configureSource: true, }); break; case 'nothing_space': @@ -48,7 +48,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('infrastructure'); expect(uiCapabilities.value!.infrastructure).to.eql({ show: false, - save: false, + configureSource: false, }); break; default: diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/logs.ts b/x-pack/test/ui_capabilities/spaces_only/tests/logs.ts index 9dd18a20d8170..18f43ac5b687b 100644 --- a/x-pack/test/ui_capabilities/spaces_only/tests/logs.ts +++ b/x-pack/test/ui_capabilities/spaces_only/tests/logs.ts @@ -39,7 +39,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: true, - save: true, + configureSource: true, }); break; case 'nothing_space': @@ -48,7 +48,7 @@ export default function advancedSettingsTests({ expect(uiCapabilities.value).to.have.property('logs'); expect(uiCapabilities.value!.logs).to.eql({ show: false, - save: false, + configureSource: false, }); break; default: From daea6a3db65b1c8ac3da3637359034a25ac7a193 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 22 Feb 2019 14:51:53 -0800 Subject: [PATCH 17/34] Fixing privileges API --- .../api_integration/apis/security/privileges.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 9265f889f0dab..ee0ac33fccf32 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -391,6 +391,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', + 'ui:apm/show', ], }, maps: { @@ -494,7 +495,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:config/get', 'saved_object:config/find', 'ui:infrastructure/show', - 'ui:infrastructure/save', + 'ui:infrastructure/configureSource', ], read: [ 'login:', @@ -531,7 +532,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:config/get', 'saved_object:config/find', 'ui:logs/show', - 'ui:logs/save', + 'ui:logs/configureSource', ], read: [ 'login:', @@ -675,6 +676,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:apm', 'ui:catalogue/apm', 'ui:navLinks/apm', + 'ui:apm/show', 'app:maps', 'ui:catalogue/maps', 'ui:navLinks/maps', @@ -705,11 +707,11 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:infrastructure-ui-source/update', 'saved_object:infrastructure-ui-source/delete', 'ui:infrastructure/show', - 'ui:infrastructure/save', + 'ui:infrastructure/configureSource', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', 'ui:logs/show', - 'ui:logs/save', + 'ui:logs/configureSource', 'app:uptime', 'ui:catalogue/uptime', 'ui:navLinks/uptime', @@ -776,6 +778,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:apm', 'ui:catalogue/apm', 'ui:navLinks/apm', + 'ui:apm/show', 'app:maps', 'ui:catalogue/maps', 'ui:navLinks/maps', @@ -899,6 +902,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:apm', 'ui:catalogue/apm', 'ui:navLinks/apm', + 'ui:apm/show', 'app:maps', 'ui:catalogue/maps', 'ui:navLinks/maps', @@ -929,11 +933,11 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:infrastructure-ui-source/update', 'saved_object:infrastructure-ui-source/delete', 'ui:infrastructure/show', - 'ui:infrastructure/save', + 'ui:infrastructure/configureSource', 'ui:catalogue/infralogging', 'ui:navLinks/infra:logs', 'ui:logs/show', - 'ui:logs/save', + 'ui:logs/configureSource', 'app:uptime', 'ui:catalogue/uptime', 'ui:navLinks/uptime', @@ -1000,6 +1004,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:apm', 'ui:catalogue/apm', 'ui:navLinks/apm', + 'ui:apm/show', 'app:maps', 'ui:catalogue/maps', 'ui:navLinks/maps', From f53271c1308f5bc705ebdc54e14fc71156df9bb2 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 22 Feb 2019 14:53:38 -0800 Subject: [PATCH 18/34] Forcing logout --- .../feature_controls/infrastructure_security.ts | 8 ++++---- .../apps/infra/feature_controls/logs_security.ts | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts index 040cd5bd0eda8..da8b173f6f683 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts @@ -38,7 +38,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa full_name: 'test user', }); - await PageObjects.security.logout(); + await PageObjects.security.forceLogout(); await PageObjects.security.login( 'global_infrastructure_all_user', @@ -53,7 +53,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await Promise.all([ security.role.delete('global_infrastructure_all_role'), security.user.delete('global_infrastructure_all_user'), - PageObjects.security.logout(), + PageObjects.security.forceLogout(), ]); }); @@ -143,7 +143,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa full_name: 'test user', }); - await PageObjects.security.logout(); + await PageObjects.security.forceLogout(); await PageObjects.security.login( 'global_infrastructure_read_user', @@ -158,7 +158,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await Promise.all([ security.role.delete('global_infrastructure_read_role'), security.user.delete('global_infrastructure_read_user'), - PageObjects.security.logout(), + PageObjects.security.forceLogout(), ]); }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts index 8f4976a597129..bd034f2a3c791 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts @@ -37,7 +37,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa full_name: 'test user', }); - await PageObjects.security.logout(); + await PageObjects.security.forceLogout(); await PageObjects.security.login('global_logs_all_user', 'global_logs_all_user-password', { expectSpaceSelector: false, @@ -48,7 +48,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await Promise.all([ security.role.delete('global_logs_all_role'), security.user.delete('global_logs_all_user'), - PageObjects.security.logout(), + PageObjects.security.forceLogout(), ]); }); @@ -94,7 +94,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa full_name: 'test user', }); - await PageObjects.security.logout(); + await PageObjects.security.forceLogout(); await PageObjects.security.login( 'global_logs_read_user', @@ -109,7 +109,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await Promise.all([ security.role.delete('global_logs_read_role'), security.user.delete('global_logs_read_user'), - PageObjects.security.logout(), + PageObjects.security.forceLogout(), ]); }); @@ -155,7 +155,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa full_name: 'test user', }); - await PageObjects.security.logout(); + await PageObjects.security.forceLogout(); await PageObjects.security.login( 'global_logs_no_privileges_user', @@ -170,7 +170,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await Promise.all([ security.role.delete('global_logs_no_privileges_role'), security.user.delete('global_logs_no_privileges_user'), - PageObjects.security.logout(), + PageObjects.security.forceLogout(), ]); }); From 1b3b41866251fdfa61dcd019df0a33a7e4edd42c Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 25 Feb 2019 10:28:02 -0800 Subject: [PATCH 19/34] Fixing comma issue introduced by merge --- .../components/waffle/node_context_menu.tsx | 64 +++++++++++-------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index 7baf81b474d3c..be9deb4e87fd7 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -24,33 +24,44 @@ interface Props { uiCapabilities: UICapabilities; } -export const NodeContextMenu = injectUICapabilities(injectI18n( - ({ options, timeRange, children, node, isPopoverOpen, closePopover, nodeType, intl, uiCapabilities }: Props) => { - // Due to the changing nature of the fields between APM and this UI, - // We need to have some exceptions until 7.0 & ECS is finalized. Reference - // #26620 for the details for these fields. - // TODO: This is tech debt, remove it after 7.0 & ECS migration. - const APM_FIELDS = { - [InfraNodeType.host]: 'host.hostname', - [InfraNodeType.container]: 'container.id', - [InfraNodeType.pod]: 'kubernetes.pod.uid', - }; +export const NodeContextMenu = injectUICapabilities( + injectI18n( + ({ + options, + timeRange, + children, + node, + isPopoverOpen, + closePopover, + nodeType, + intl, + uiCapabilities, + }: Props) => { + // Due to the changing nature of the fields between APM and this UI, + // We need to have some exceptions until 7.0 & ECS is finalized. Reference + // #26620 for the details for these fields. + // TODO: This is tech debt, remove it after 7.0 & ECS migration. + const APM_FIELDS = { + [InfraNodeType.host]: 'host.hostname', + [InfraNodeType.container]: 'container.id', + [InfraNodeType.pod]: 'kubernetes.pod.uid', + }; - const nodeLogsUrl = node.id - ? getNodeLogsUrl({ - nodeType, - nodeId: node.id, - time: timeRange.to, - }) - : undefined; - const nodeDetailUrl = node.id - ? getNodeDetailUrl({ - nodeType, - nodeId: node.id, - from: timeRange.from, - to: timeRange.to, - }) - : undefined; + const nodeLogsUrl = node.id + ? getNodeLogsUrl({ + nodeType, + nodeId: node.id, + time: timeRange.to, + }) + : undefined; + const nodeDetailUrl = node.id + ? getNodeDetailUrl({ + nodeType, + nodeId: node.id, + from: timeRange.from, + to: timeRange.to, + }) + : undefined; const apmTracesUrl = uiCapabilities.apm.show ? { @@ -112,5 +123,4 @@ export const NodeContextMenu = injectUICapabilities(injectI18n( ); } ) -) ); From a51083d1c5f75cec9f04ae5fcfdcf9f17ec1c8f6 Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 25 Feb 2019 12:14:38 -0800 Subject: [PATCH 20/34] Fix merge conflicts and loading/unloading esarchives more consistently --- .../components/waffle/node_context_menu.tsx | 15 ++++++++------- x-pack/plugins/infra/public/pages/home/index.tsx | 7 ++++++- x-pack/plugins/infra/public/pages/logs/logs.tsx | 9 +++++++-- .../feature_controls/infrastructure_spaces.ts | 6 +++++- .../apps/infra/feature_controls/logs_security.ts | 4 ++++ 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx index be9deb4e87fd7..fa4038ad6abce 100644 --- a/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -47,13 +47,14 @@ export const NodeContextMenu = injectUICapabilities( [InfraNodeType.pod]: 'kubernetes.pod.uid', }; - const nodeLogsUrl = node.id - ? getNodeLogsUrl({ - nodeType, - nodeId: node.id, - time: timeRange.to, - }) - : undefined; + const nodeLogsUrl = + node.id && uiCapabilities.logs.show + ? getNodeLogsUrl({ + nodeType, + nodeId: node.id, + time: timeRange.to, + }) + : undefined; const nodeDetailUrl = node.id ? getNodeDetailUrl({ nodeType, diff --git a/x-pack/plugins/infra/public/pages/home/index.tsx b/x-pack/plugins/infra/public/pages/home/index.tsx index 8c44653437380..eec4c9c59336b 100644 --- a/x-pack/plugins/infra/public/pages/home/index.tsx +++ b/x-pack/plugins/infra/public/pages/home/index.tsx @@ -100,6 +100,7 @@ export const HomePage = injectUICapabilities( href={`${basePath}/app/kibana#/home/tutorial_directory/metrics`} color="primary" fill + data-test-subj="infrastructureViewSetupInstructionsButton" > {intl.formatMessage({ id: @@ -112,7 +113,11 @@ export const HomePage = injectUICapabilities( {({ enable }) => ( - + {intl.formatMessage({ id: 'xpack.infra.configureSourceActionLabel', defaultMessage: 'Change source configuration', diff --git a/x-pack/plugins/infra/public/pages/logs/logs.tsx b/x-pack/plugins/infra/public/pages/logs/logs.tsx index 21e76c67f65c3..942caa56a63d7 100644 --- a/x-pack/plugins/infra/public/pages/logs/logs.tsx +++ b/x-pack/plugins/infra/public/pages/logs/logs.tsx @@ -45,7 +45,7 @@ export const LogsPage = injectUICapabilities( const { intl, uiCapabilities } = this.props; return ( - +
{intl.formatMessage({ id: @@ -144,7 +145,11 @@ export const LogsPage = injectUICapabilities( {({ enable }) => ( - + {intl.formatMessage({ id: 'xpack.infra.configureSourceActionLabel', defaultMessage: 'Change source configuration', diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts index 30d4fb30b3488..bc481da9756ee 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts @@ -18,7 +18,11 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa describe('infrastructure spaces', () => { before(async () => { - await esArchiver.loadIfNeeded('infra/metrics_and_logs'); + await esArchiver.load('infra/metrics_and_logs'); + }); + + after(async () => { + await esArchiver.unload('infra/metrics_and_logs'); }); describe('space with no features disabled', () => { diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts index bd034f2a3c791..a9a319ebad4e0 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts @@ -9,12 +9,16 @@ import { KibanaFunctionalTestDefaultProviders } from 'x-pack/test/types/provider // tslint:disable no-default-export export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { + const esArchiver = getService('esArchiver'); const security = getService('security'); const PageObjects = getPageObjects(['common', 'infraHome', 'security']); const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); describe('logs security', () => { + before(async () => { + esArchiver.load('empty_kibana'); + }); describe('global logs all privileges', () => { before(async () => { await security.role.create('global_logs_all_role', { From fc1ea1aba7d0aade88555582eb841eff519f8607 Mon Sep 17 00:00:00 2001 From: kobelb Date: Wed, 27 Feb 2019 06:00:26 -0800 Subject: [PATCH 21/34] Removing unnecessary !! --- .../source_configuration/source_configuration_flyout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx index 6febf949c83a4..4bee54feaee91 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx @@ -144,7 +144,7 @@ export const SourceConfigurationFlyout = injectI18n( )} - {!!shouldAllowEdit && ( + {shouldAllowEdit && ( {isLoading ? ( From c213bc341f8fcb79c67b9c35a335e00432236b9b Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 1 Mar 2019 08:59:04 -0800 Subject: [PATCH 22/34] Fixing saved object management tests --- .../security_and_spaces/tests/saved_objects_management.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/saved_objects_management.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/saved_objects_management.ts index 6721b2432e6c2..ed8b12c44fd10 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/saved_objects_management.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/saved_objects_management.ts @@ -60,6 +60,7 @@ export default function savedObjectsManagementTests({ 'dashboard', 'timelion-sheet', 'url', + 'infrastructure-ui-source', ], }) ); @@ -85,6 +86,7 @@ export default function savedObjectsManagementTests({ 'dashboard', 'timelion-sheet', 'url', + 'infrastructure-ui-source', ], }) ); From 6202363c4948d5e6d747b0333aadf5b550979019 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 1 Mar 2019 10:27:33 -0800 Subject: [PATCH 23/34] Fixing more tests --- .../security_only/tests/saved_objects_management.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/x-pack/test/ui_capabilities/security_only/tests/saved_objects_management.ts b/x-pack/test/ui_capabilities/security_only/tests/saved_objects_management.ts index 8a8b9cfd0f9c4..8cff3014b985c 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/saved_objects_management.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/saved_objects_management.ts @@ -53,6 +53,7 @@ export default function savedObjectsManagementTests({ 'dashboard', 'timelion-sheet', 'url', + 'infrastructure-ui-source', ], }) ); @@ -73,6 +74,7 @@ export default function savedObjectsManagementTests({ 'dashboard', 'timelion-sheet', 'url', + 'infrastructure-ui-source', ], }) ); @@ -238,6 +240,7 @@ export default function savedObjectsManagementTests({ expect(uiCapabilities.value).to.have.property('savedObjectsManagement'); expect(uiCapabilities.value!.savedObjectsManagement).to.eql( savedObjectsManagementBuilder.only({ + all: 'infrastructure-ui-source', read: 'config', }) ); @@ -247,7 +250,7 @@ export default function savedObjectsManagementTests({ expect(uiCapabilities.value).to.have.property('savedObjectsManagement'); expect(uiCapabilities.value!.savedObjectsManagement).to.eql( savedObjectsManagementBuilder.only({ - read: 'config', + read: ['infrastructure-ui-source', 'config'], }) ); break; @@ -256,6 +259,7 @@ export default function savedObjectsManagementTests({ expect(uiCapabilities.value).to.have.property('savedObjectsManagement'); expect(uiCapabilities.value!.savedObjectsManagement).to.eql( savedObjectsManagementBuilder.only({ + all: 'infrastructure-ui-source', read: 'config', }) ); @@ -265,7 +269,7 @@ export default function savedObjectsManagementTests({ expect(uiCapabilities.value).to.have.property('savedObjectsManagement'); expect(uiCapabilities.value!.savedObjectsManagement).to.eql( savedObjectsManagementBuilder.only({ - read: 'config', + read: ['infrastructure-ui-source', 'config'], }) ); break; From 4776f1fc862317fc09af15fd7f30111d0b395b1f Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 1 Mar 2019 11:49:42 -0800 Subject: [PATCH 24/34] Using the new context APIs --- .../react/inject_ui_capabilities.tsx | 10 +++---- .../react/ui_capabilities_context.ts | 27 +++++++++++++++++++ .../react/ui_capabilities_provider.tsx | 24 +++++------------ 3 files changed, 37 insertions(+), 24 deletions(-) create mode 100644 src/legacy/ui/public/capabilities/react/ui_capabilities_context.ts diff --git a/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx index 07583b85f725f..386e5b799ed6a 100644 --- a/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx +++ b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx @@ -17,9 +17,9 @@ * under the License. */ -import PropTypes from 'prop-types'; import React, { Component, ComponentClass, ComponentType } from 'react'; import { UICapabilities } from '../ui_capabilities'; +import { UICapabilitiesContext } from './ui_capabilities_context'; function getDisplayName(component: ComponentType) { return component.displayName || component.name || 'Component'; @@ -39,18 +39,14 @@ export function injectUICapabilities

( public static WrappedComponent: ComponentType

= WrappedComponent; - public static contextTypes = { - uiCapabilities: PropTypes.object.isRequired, - }; + public static contextType = UICapabilitiesContext; constructor(props: any, context: any) { super(props, context); } public render() { - return ( - - ); + return ; } } return InjectUICapabilities; diff --git a/src/legacy/ui/public/capabilities/react/ui_capabilities_context.ts b/src/legacy/ui/public/capabilities/react/ui_capabilities_context.ts new file mode 100644 index 0000000000000..4a6ecc2d7da87 --- /dev/null +++ b/src/legacy/ui/public/capabilities/react/ui_capabilities_context.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { UICapabilities } from '../ui_capabilities'; + +export const UICapabilitiesContext = React.createContext({ + navLinks: {}, + catalogue: {}, + management: {}, +}); diff --git a/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx index cda046fd162c5..9fa493becee06 100644 --- a/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx +++ b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx @@ -17,32 +17,22 @@ * under the License. */ -import PropTypes from 'prop-types'; import React, { ReactNode } from 'react'; -import { uiCapabilities, UICapabilities } from '../ui_capabilities'; +import { uiCapabilities } from '../ui_capabilities'; +import { UICapabilitiesContext } from './ui_capabilities_context'; interface Props { children: ReactNode; } -interface ProviderContext { - uiCapabilities: UICapabilities; -} - export class UICapabilitiesProvider extends React.Component { public static displayName: string = 'UICapabilitiesProvider'; - public static childContextTypes = { - uiCapabilities: PropTypes.object.isRequired, - }; - - public getChildContext(): ProviderContext { - return { - uiCapabilities, - }; - } - public render() { - return React.Children.only(this.props.children); + return ( + + {this.props.children} + + ); } } From ab1e930f5ee025182b79eaea0dd77f7d6d3e66e3 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 1 Mar 2019 12:02:32 -0800 Subject: [PATCH 25/34] Revert "Using the new context APIs" This reverts commit 4776f1fc862317fc09af15fd7f30111d0b395b1f. --- .../react/inject_ui_capabilities.tsx | 10 ++++--- .../react/ui_capabilities_context.ts | 27 ------------------- .../react/ui_capabilities_provider.tsx | 24 ++++++++++++----- 3 files changed, 24 insertions(+), 37 deletions(-) delete mode 100644 src/legacy/ui/public/capabilities/react/ui_capabilities_context.ts diff --git a/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx index 386e5b799ed6a..07583b85f725f 100644 --- a/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx +++ b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx @@ -17,9 +17,9 @@ * under the License. */ +import PropTypes from 'prop-types'; import React, { Component, ComponentClass, ComponentType } from 'react'; import { UICapabilities } from '../ui_capabilities'; -import { UICapabilitiesContext } from './ui_capabilities_context'; function getDisplayName(component: ComponentType) { return component.displayName || component.name || 'Component'; @@ -39,14 +39,18 @@ export function injectUICapabilities

( public static WrappedComponent: ComponentType

= WrappedComponent; - public static contextType = UICapabilitiesContext; + public static contextTypes = { + uiCapabilities: PropTypes.object.isRequired, + }; constructor(props: any, context: any) { super(props, context); } public render() { - return ; + return ( + + ); } } return InjectUICapabilities; diff --git a/src/legacy/ui/public/capabilities/react/ui_capabilities_context.ts b/src/legacy/ui/public/capabilities/react/ui_capabilities_context.ts deleted file mode 100644 index 4a6ecc2d7da87..0000000000000 --- a/src/legacy/ui/public/capabilities/react/ui_capabilities_context.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { UICapabilities } from '../ui_capabilities'; - -export const UICapabilitiesContext = React.createContext({ - navLinks: {}, - catalogue: {}, - management: {}, -}); diff --git a/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx index 9fa493becee06..cda046fd162c5 100644 --- a/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx +++ b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx @@ -17,22 +17,32 @@ * under the License. */ +import PropTypes from 'prop-types'; import React, { ReactNode } from 'react'; -import { uiCapabilities } from '../ui_capabilities'; -import { UICapabilitiesContext } from './ui_capabilities_context'; +import { uiCapabilities, UICapabilities } from '../ui_capabilities'; interface Props { children: ReactNode; } +interface ProviderContext { + uiCapabilities: UICapabilities; +} + export class UICapabilitiesProvider extends React.Component { public static displayName: string = 'UICapabilitiesProvider'; + public static childContextTypes = { + uiCapabilities: PropTypes.object.isRequired, + }; + + public getChildContext(): ProviderContext { + return { + uiCapabilities, + }; + } + public render() { - return ( - - {this.props.children} - - ); + return React.Children.only(this.props.children); } } From 28612d09512b8710117148021e607d8a78b984dd Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 1 Mar 2019 13:55:05 -0800 Subject: [PATCH 26/34] Adding future version of ui capabilities react provider --- .../public/capabilities/react/future/index.ts | 22 ++++ .../future/inject_ui_capabilities.test.tsx | 117 ++++++++++++++++++ .../react/future/inject_ui_capabilities.tsx | 53 ++++++++ .../react/future/ui_capabilities_context.ts | 27 ++++ .../react/future/ui_capabilities_provider.tsx | 38 ++++++ 5 files changed, 257 insertions(+) create mode 100644 src/legacy/ui/public/capabilities/react/future/index.ts create mode 100644 src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.test.tsx create mode 100644 src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.tsx create mode 100644 src/legacy/ui/public/capabilities/react/future/ui_capabilities_context.ts create mode 100644 src/legacy/ui/public/capabilities/react/future/ui_capabilities_provider.tsx diff --git a/src/legacy/ui/public/capabilities/react/future/index.ts b/src/legacy/ui/public/capabilities/react/future/index.ts new file mode 100644 index 0000000000000..5829395be5b41 --- /dev/null +++ b/src/legacy/ui/public/capabilities/react/future/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { UICapabilitiesProvider } from './ui_capabilities_provider'; +export { injectUICapabilities } from './inject_ui_capabilities'; +export { UICapabilities } from '../../index'; diff --git a/src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.test.tsx b/src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.test.tsx new file mode 100644 index 0000000000000..48441c94a031f --- /dev/null +++ b/src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.test.tsx @@ -0,0 +1,117 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +jest.mock('ui/chrome', () => ({ + getInjected(key: string) { + if (key === 'uiCapabilities') { + return { + uiCapability1: true, + uiCapability2: { + nestedProp: 'nestedValue', + }, + }; + } + }, +})); + +import { mount } from 'enzyme'; +import React from 'react'; +import { UICapabilities } from '..'; +import { injectUICapabilities } from './inject_ui_capabilities'; +import { UICapabilitiesProvider } from './ui_capabilities_provider'; + +describe('injectUICapabilities', () => { + it('provides UICapabilities to SFCs', () => { + interface SFCProps { + uiCapabilities: UICapabilities; + } + + const MySFC = injectUICapabilities(({ uiCapabilities }: SFCProps) => { + return {uiCapabilities.uiCapability2.nestedProp}; + }); + + const wrapper = mount( + + + + ); + + expect(wrapper).toMatchInlineSnapshot(` + + + + + nestedValue + + + + +`); + }); + + it('provides UICapabilities to class components', () => { + interface ClassProps { + uiCapabilities: UICapabilities; + } + + class MyClassComponent extends React.Component { + public render() { + return {this.props.uiCapabilities.uiCapability2.nestedProp}; + } + } + + const WrappedComponent = injectUICapabilities(MyClassComponent); + + const wrapper = mount( + + + + ); + + expect(wrapper).toMatchInlineSnapshot(` + + + + + nestedValue + + + + +`); + }); +}); diff --git a/src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.tsx b/src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.tsx new file mode 100644 index 0000000000000..1cb7f6f1fe44e --- /dev/null +++ b/src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.tsx @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { Component, ComponentClass, ComponentType } from 'react'; +import { UICapabilities } from '../../ui_capabilities'; +import { UICapabilitiesContext } from './ui_capabilities_context'; + +function getDisplayName(component: ComponentType) { + return component.displayName || component.name || 'Component'; +} + +interface InjectedProps { + uiCapabilities: UICapabilities; +} + +export function injectUICapabilities

( + WrappedComponent: ComponentType

+): ComponentClass>> & { + WrappedComponent: ComponentType

; +} { + class InjectUICapabilities extends Component { + public static displayName = `InjectUICapabilities(${getDisplayName(WrappedComponent)})`; + + public static WrappedComponent: ComponentType

= WrappedComponent; + + public static contextType = UICapabilitiesContext; + + constructor(props: any, context: any) { + super(props, context); + } + + public render() { + return ; + } + } + return InjectUICapabilities; +} diff --git a/src/legacy/ui/public/capabilities/react/future/ui_capabilities_context.ts b/src/legacy/ui/public/capabilities/react/future/ui_capabilities_context.ts new file mode 100644 index 0000000000000..46b9de85daf39 --- /dev/null +++ b/src/legacy/ui/public/capabilities/react/future/ui_capabilities_context.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { UICapabilities } from '../../ui_capabilities'; + +export const UICapabilitiesContext = React.createContext({ + navLinks: {}, + catalogue: {}, + management: {}, +}); diff --git a/src/legacy/ui/public/capabilities/react/future/ui_capabilities_provider.tsx b/src/legacy/ui/public/capabilities/react/future/ui_capabilities_provider.tsx new file mode 100644 index 0000000000000..afef321546730 --- /dev/null +++ b/src/legacy/ui/public/capabilities/react/future/ui_capabilities_provider.tsx @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { ReactNode } from 'react'; +import { uiCapabilities } from '../../ui_capabilities'; +import { UICapabilitiesContext } from './ui_capabilities_context'; + +interface Props { + children: ReactNode; +} + +export class UICapabilitiesProvider extends React.Component { + public static displayName: string = 'UICapabilitiesProvider'; + + public render() { + return ( + + {this.props.children} + + ); + } +} From a8d9c41676efb0fb24af771b4d9f1a518b94ae63 Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 4 Mar 2019 12:43:43 -0800 Subject: [PATCH 27/34] Switching the order of the HOC's for infra and making the future the default --- .../ui/public/capabilities/react/index.ts | 2 +- .../react/inject_ui_capabilities.tsx | 10 +++----- .../react/{future => legacy}/index.ts | 2 +- .../inject_ui_capabilities.test.tsx | 0 .../inject_ui_capabilities.tsx | 10 +++++--- .../ui_capabilities_provider.tsx | 24 +++++++++++++------ .../{future => }/ui_capabilities_context.ts | 2 +- .../react/ui_capabilities_provider.tsx | 24 ++++++------------- .../infra/public/pages/metrics/index.tsx | 4 ++-- 9 files changed, 39 insertions(+), 39 deletions(-) rename src/legacy/ui/public/capabilities/react/{future => legacy}/index.ts (94%) rename src/legacy/ui/public/capabilities/react/{future => legacy}/inject_ui_capabilities.test.tsx (100%) rename src/legacy/ui/public/capabilities/react/{future => legacy}/inject_ui_capabilities.tsx (87%) rename src/legacy/ui/public/capabilities/react/{future => legacy}/ui_capabilities_provider.tsx (71%) rename src/legacy/ui/public/capabilities/react/{future => }/ui_capabilities_context.ts (94%) diff --git a/src/legacy/ui/public/capabilities/react/index.ts b/src/legacy/ui/public/capabilities/react/index.ts index b25edf68264c6..ccf009dfea1fc 100644 --- a/src/legacy/ui/public/capabilities/react/index.ts +++ b/src/legacy/ui/public/capabilities/react/index.ts @@ -19,4 +19,4 @@ export { UICapabilitiesProvider } from './ui_capabilities_provider'; export { injectUICapabilities } from './inject_ui_capabilities'; -export { UICapabilities } from '../index'; +export { UICapabilities } from '../ui_capabilities'; diff --git a/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx index 07583b85f725f..386e5b799ed6a 100644 --- a/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx +++ b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.tsx @@ -17,9 +17,9 @@ * under the License. */ -import PropTypes from 'prop-types'; import React, { Component, ComponentClass, ComponentType } from 'react'; import { UICapabilities } from '../ui_capabilities'; +import { UICapabilitiesContext } from './ui_capabilities_context'; function getDisplayName(component: ComponentType) { return component.displayName || component.name || 'Component'; @@ -39,18 +39,14 @@ export function injectUICapabilities

( public static WrappedComponent: ComponentType

= WrappedComponent; - public static contextTypes = { - uiCapabilities: PropTypes.object.isRequired, - }; + public static contextType = UICapabilitiesContext; constructor(props: any, context: any) { super(props, context); } public render() { - return ( - - ); + return ; } } return InjectUICapabilities; diff --git a/src/legacy/ui/public/capabilities/react/future/index.ts b/src/legacy/ui/public/capabilities/react/legacy/index.ts similarity index 94% rename from src/legacy/ui/public/capabilities/react/future/index.ts rename to src/legacy/ui/public/capabilities/react/legacy/index.ts index 5829395be5b41..b89cfa80a2725 100644 --- a/src/legacy/ui/public/capabilities/react/future/index.ts +++ b/src/legacy/ui/public/capabilities/react/legacy/index.ts @@ -19,4 +19,4 @@ export { UICapabilitiesProvider } from './ui_capabilities_provider'; export { injectUICapabilities } from './inject_ui_capabilities'; -export { UICapabilities } from '../../index'; +export { UICapabilities } from '../../ui_capabilities'; diff --git a/src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.test.tsx b/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx similarity index 100% rename from src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.test.tsx rename to src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx diff --git a/src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.tsx b/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.tsx similarity index 87% rename from src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.tsx rename to src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.tsx index 1cb7f6f1fe44e..3bc9c570ddf35 100644 --- a/src/legacy/ui/public/capabilities/react/future/inject_ui_capabilities.tsx +++ b/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.tsx @@ -17,9 +17,9 @@ * under the License. */ +import PropTypes from 'prop-types'; import React, { Component, ComponentClass, ComponentType } from 'react'; import { UICapabilities } from '../../ui_capabilities'; -import { UICapabilitiesContext } from './ui_capabilities_context'; function getDisplayName(component: ComponentType) { return component.displayName || component.name || 'Component'; @@ -39,14 +39,18 @@ export function injectUICapabilities

( public static WrappedComponent: ComponentType

= WrappedComponent; - public static contextType = UICapabilitiesContext; + public static contextTypes = { + uiCapabilities: PropTypes.object.isRequired, + }; constructor(props: any, context: any) { super(props, context); } public render() { - return ; + return ( + + ); } } return InjectUICapabilities; diff --git a/src/legacy/ui/public/capabilities/react/future/ui_capabilities_provider.tsx b/src/legacy/ui/public/capabilities/react/legacy/ui_capabilities_provider.tsx similarity index 71% rename from src/legacy/ui/public/capabilities/react/future/ui_capabilities_provider.tsx rename to src/legacy/ui/public/capabilities/react/legacy/ui_capabilities_provider.tsx index afef321546730..dd948447c1cf7 100644 --- a/src/legacy/ui/public/capabilities/react/future/ui_capabilities_provider.tsx +++ b/src/legacy/ui/public/capabilities/react/legacy/ui_capabilities_provider.tsx @@ -17,22 +17,32 @@ * under the License. */ +import PropTypes from 'prop-types'; import React, { ReactNode } from 'react'; -import { uiCapabilities } from '../../ui_capabilities'; -import { UICapabilitiesContext } from './ui_capabilities_context'; +import { uiCapabilities, UICapabilities } from '../../ui_capabilities'; interface Props { children: ReactNode; } +interface ProviderContext { + uiCapabilities: UICapabilities; +} + export class UICapabilitiesProvider extends React.Component { public static displayName: string = 'UICapabilitiesProvider'; + public static childContextTypes = { + uiCapabilities: PropTypes.object.isRequired, + }; + + public getChildContext(): ProviderContext { + return { + uiCapabilities, + }; + } + public render() { - return ( - - {this.props.children} - - ); + return React.Children.only(this.props.children); } } diff --git a/src/legacy/ui/public/capabilities/react/future/ui_capabilities_context.ts b/src/legacy/ui/public/capabilities/react/ui_capabilities_context.ts similarity index 94% rename from src/legacy/ui/public/capabilities/react/future/ui_capabilities_context.ts rename to src/legacy/ui/public/capabilities/react/ui_capabilities_context.ts index 46b9de85daf39..4a6ecc2d7da87 100644 --- a/src/legacy/ui/public/capabilities/react/future/ui_capabilities_context.ts +++ b/src/legacy/ui/public/capabilities/react/ui_capabilities_context.ts @@ -18,7 +18,7 @@ */ import React from 'react'; -import { UICapabilities } from '../../ui_capabilities'; +import { UICapabilities } from '../ui_capabilities'; export const UICapabilitiesContext = React.createContext({ navLinks: {}, diff --git a/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx index cda046fd162c5..9fa493becee06 100644 --- a/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx +++ b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx @@ -17,32 +17,22 @@ * under the License. */ -import PropTypes from 'prop-types'; import React, { ReactNode } from 'react'; -import { uiCapabilities, UICapabilities } from '../ui_capabilities'; +import { uiCapabilities } from '../ui_capabilities'; +import { UICapabilitiesContext } from './ui_capabilities_context'; interface Props { children: ReactNode; } -interface ProviderContext { - uiCapabilities: UICapabilities; -} - export class UICapabilitiesProvider extends React.Component { public static displayName: string = 'UICapabilitiesProvider'; - public static childContextTypes = { - uiCapabilities: PropTypes.object.isRequired, - }; - - public getChildContext(): ProviderContext { - return { - uiCapabilities, - }; - } - public render() { - return React.Children.only(this.props.children); + return ( + + {this.props.children} + + ); } } diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index c176e9f6b37ce..fbb5336deab91 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -62,8 +62,8 @@ interface Props { uiCapabilities: UICapabilities; } -export const MetricDetail = withTheme( - injectUICapabilities( +export const MetricDetail = injectUICapabilities( + withTheme( injectI18n( class extends React.PureComponent { public static displayName = 'MetricDetailPage'; From 35829c13380751dfe03b220195fa48d3662243f6 Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 4 Mar 2019 12:59:39 -0800 Subject: [PATCH 28/34] Applying Felix's PR feedback --- .../fields_configuration_panel.tsx | 19 ++++++++++++------- .../indices_configuration_panel.tsx | 10 ++++++---- .../name_configuration_panel.tsx | 7 ++++--- .../source_configuration_flyout.tsx | 8 ++++---- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx b/x-pack/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx index 38e7ea0911082..c48562b355d83 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx @@ -14,7 +14,7 @@ interface FieldsConfigurationPanelProps { containerFieldProps: InputFieldProps; hostFieldProps: InputFieldProps; isLoading: boolean; - disabled: boolean; + readOnly: boolean; podFieldProps: InputFieldProps; tiebreakerFieldProps: InputFieldProps; timestampFieldProps: InputFieldProps; @@ -24,7 +24,7 @@ export const FieldsConfigurationPanel = ({ containerFieldProps, hostFieldProps, isLoading, - disabled, + readOnly, podFieldProps, tiebreakerFieldProps, timestampFieldProps, @@ -61,7 +61,8 @@ export const FieldsConfigurationPanel = ({ > @@ -88,7 +89,8 @@ export const FieldsConfigurationPanel = ({ > @@ -115,7 +117,8 @@ export const FieldsConfigurationPanel = ({ > @@ -142,7 +145,8 @@ export const FieldsConfigurationPanel = ({ > @@ -169,7 +173,8 @@ export const FieldsConfigurationPanel = ({ > diff --git a/x-pack/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx b/x-pack/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx index f1da43ca2c1a8..fede05cb53f9f 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx @@ -12,14 +12,14 @@ import { InputFieldProps } from './source_configuration_form_state'; interface IndicesConfigurationPanelProps { isLoading: boolean; - disabled: boolean; + readOnly: boolean; logAliasFieldProps: InputFieldProps; metricAliasFieldProps: InputFieldProps; } export const IndicesConfigurationPanel = ({ isLoading, - disabled, + readOnly, logAliasFieldProps, metricAliasFieldProps, }: IndicesConfigurationPanelProps) => ( @@ -55,7 +55,8 @@ export const IndicesConfigurationPanel = ({ > @@ -82,7 +83,8 @@ export const IndicesConfigurationPanel = ({ > diff --git a/x-pack/plugins/infra/public/components/source_configuration/name_configuration_panel.tsx b/x-pack/plugins/infra/public/components/source_configuration/name_configuration_panel.tsx index beb19ec14d2f6..2590b35b7f69d 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/name_configuration_panel.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/name_configuration_panel.tsx @@ -12,13 +12,13 @@ import { InputFieldProps } from './source_configuration_form_state'; interface NameConfigurationPanelProps { isLoading: boolean; - disabled: boolean; + readOnly: boolean; nameFieldProps: InputFieldProps; } export const NameConfigurationPanel = ({ isLoading, - disabled, + readOnly, nameFieldProps, }: NameConfigurationPanelProps) => ( @@ -41,7 +41,8 @@ export const NameConfigurationPanel = ({ > diff --git a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx index 4bee54feaee91..18dd9d393979d 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_flyout.tsx @@ -82,7 +82,7 @@ export const SourceConfigurationFlyout = injectI18n( ) : ( )} @@ -91,13 +91,13 @@ export const SourceConfigurationFlyout = injectI18n( @@ -106,7 +106,7 @@ export const SourceConfigurationFlyout = injectI18n( containerFieldProps={getFieldFieldProps('container')} hostFieldProps={getFieldFieldProps('host')} isLoading={isLoading} - disabled={!shouldAllowEdit} + readOnly={!shouldAllowEdit} podFieldProps={getFieldFieldProps('pod')} tiebreakerFieldProps={getFieldFieldProps('tiebreaker')} timestampFieldProps={getFieldFieldProps('timestamp')} From 2cd4a505d46887799e8f45bd9c11acbaf839cd61 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 5 Mar 2019 09:13:31 -0800 Subject: [PATCH 29/34] Protecting Infra's GraphQL APIs --- x-pack/package.json | 1 + x-pack/plugins/infra/server/kibana.index.ts | 4 + .../framework/kibana_framework_adapter.ts | 6 + .../apis/infra/feature_controls.ts | 294 ++++++++++++++++++ .../test/api_integration/apis/infra/index.js | 1 + .../test/api_integration/apis/infra/types.ts | 9 + x-pack/test/api_integration/config.js | 6 +- x-pack/test/api_integration/services/index.js | 2 +- .../services/infraops_graphql_client.js | 44 ++- yarn.lock | 21 ++ 10 files changed, 373 insertions(+), 15 deletions(-) create mode 100644 x-pack/test/api_integration/apis/infra/feature_controls.ts diff --git a/x-pack/package.json b/x-pack/package.json index b7cdaf19a4062..7375f853f5651 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -70,6 +70,7 @@ "abab": "^1.0.4", "ansi-colors": "^3.0.5", "ansicolors": "0.3.2", + "apollo-link-context": "^1.0.14", "axios": "^0.18.0", "babel-jest": "^23.6.0", "babel-plugin-inline-react-svg": "^0.5.4", diff --git a/x-pack/plugins/infra/server/kibana.index.ts b/x-pack/plugins/infra/server/kibana.index.ts index 99a52a753b374..a32f1f8bc489d 100644 --- a/x-pack/plugins/infra/server/kibana.index.ts +++ b/x-pack/plugins/infra/server/kibana.index.ts @@ -34,6 +34,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { catalogue: ['infraops'], privileges: { all: { + api: ['infra/graphql'], savedObject: { all: ['infrastructure-ui-source'], read: ['config'], @@ -41,6 +42,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { ui: ['show', 'configureSource'], }, read: { + api: ['infra/graphql'], savedObject: { all: [], read: ['config', 'infrastructure-ui-source'], @@ -61,6 +63,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { catalogue: ['infralogging'], privileges: { all: { + api: ['infra/graphql'], savedObject: { all: ['infrastructure-ui-source'], read: ['config'], @@ -68,6 +71,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { ui: ['show', 'configureSource'], }, read: { + api: ['infra/graphql'], savedObject: { all: [], read: ['config', 'infrastructure-ui-source'], diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts index 8b13cea43065e..9eb576e895906 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -56,6 +56,9 @@ export class InfraKibanaBackendFrameworkAdapter implements InfraBackendFramework schema, }), path: routePath, + route: { + tags: ['access:graphql'], + }, }, plugin: graphqlHapi, }); @@ -67,6 +70,9 @@ export class InfraKibanaBackendFrameworkAdapter implements InfraBackendFramework passHeader: `'kbn-version': '${this.version}'`, }), path: `${routePath}/graphiql`, + route: { + tags: ['access:graphql'], + }, }, plugin: graphiqlHapi, }); diff --git a/x-pack/test/api_integration/apis/infra/feature_controls.ts b/x-pack/test/api_integration/apis/infra/feature_controls.ts new file mode 100644 index 0000000000000..370b758bf1200 --- /dev/null +++ b/x-pack/test/api_integration/apis/infra/feature_controls.ts @@ -0,0 +1,294 @@ +/* + * 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 expect from 'expect.js'; +import { SecurityService, SpacesService } from 'x-pack/test/common/services'; +import { metadataQuery } from '../../../../plugins/infra/public/containers/metadata/metadata.gql_query'; +import { MetadataQuery } from '../../../../plugins/infra/public/graphql/types'; +import { KbnTestProvider } from './types'; + +// tslint:disable:no-default-export +const featureControlsTests: KbnTestProvider = ({ getService }) => { + const supertest = getService('supertestWithoutAuth'); + const security: SecurityService = getService('security'); + const spaces: SpacesService = getService('spaces'); + const clientFactory = getService('infraOpsGraphQLClientFactory'); + + const expectGraphQL404 = (result: any) => { + expect(result.response).to.be(undefined); + expect(result.error).not.to.be(undefined); + expect(result.error).to.have.property('networkError'); + expect(result.error.networkError).to.have.property('statusCode', 404); + }; + + const expectGraphQLResponse = (result: any) => { + expect(result.error).to.be(undefined); + expect(result.response).to.have.property('data'); + expect(result.response.data).to.be.an('object'); + }; + + const expectGraphIQL404 = (result: any) => { + expect(result.error).to.be(undefined); + expect(result.response).not.to.be(undefined); + expect(result.response).to.have.property('statusCode', 404); + }; + + const expectGraphIQLResponse = (result: any) => { + expect(result.error).to.be(undefined); + expect(result.response).not.to.be(undefined); + expect(result.response).to.have.property('statusCode', 200); + }; + + const executeGraphQLQuery = async (username: string, password: string, spaceId?: string) => { + const queryOptions = { + query: metadataQuery, + variables: { + sourceId: 'default', + nodeId: 'demo-stack-mysql-01', + nodeType: 'host', + }, + }; + + const basePath = spaceId ? `/s/${spaceId}` : ''; + + const client = clientFactory({ username, password, basePath }); + let error; + let response; + try { + response = await client.query(queryOptions); + } catch (err) { + error = err; + } + return { + error, + response, + }; + }; + + const executeGraphIQLRequest = async (username: string, password: string, spaceId?: string) => { + const basePath = spaceId ? `/s/${spaceId}` : ''; + + return supertest + .get(`${basePath}/api/infra/graphql/graphiql`) + .auth(username, password) + .then((response: any) => ({ error: undefined, response })) + .catch((error: any) => ({ error, response: undefined })); + }; + + describe('feature controls', () => { + it(`APIs can't be accessed by logstash-* read privileges role`, async () => { + const username = 'logstash_read'; + const roleName = 'logstash_read'; + const password = `${username}-password`; + try { + await security.role.create(roleName, { + elasticsearch: { + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + }, + }); + + await security.user.create(username, { + password, + roles: [roleName], + full_name: 'a kibana user', + }); + + const graphQLResult = await executeGraphQLQuery(username, password); + expectGraphQL404(graphQLResult); + + const graphQLIResult = await executeGraphIQLRequest(username, password); + expectGraphIQL404(graphQLIResult); + } finally { + await security.role.delete(roleName); + await security.user.delete(username); + } + }); + + it('APIs can be accessed global all with logstash-* read privileges role', async () => { + const username = 'global_all'; + const roleName = 'global_all'; + const password = `${username}-password`; + try { + await security.role.create(roleName, { + elasticsearch: { + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + }, + kibana: [ + { + base: ['all'], + spaces: ['*'], + }, + ], + }); + + await security.user.create(username, { + password, + roles: [roleName], + full_name: 'a kibana user', + }); + + const graphQLResult = await executeGraphQLQuery(username, password); + expectGraphQLResponse(graphQLResult); + + const graphQLIResult = await executeGraphIQLRequest(username, password); + expectGraphIQLResponse(graphQLIResult); + } finally { + await security.role.delete(roleName); + await security.user.delete(username); + } + }); + + // this could be any role which doesn't have access to the infra feature + it(`APIs can't be accessed by dashboard all with logstash-* read privileges role`, async () => { + const username = 'dashboard_all'; + const roleName = 'dashboard_all'; + const password = `${username}-password`; + try { + await security.role.create(roleName, { + elasticsearch: { + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + }, + kibana: [ + { + feature: { + dashboard: ['all'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create(username, { + password, + roles: [roleName], + full_name: 'a kibana user', + }); + + const graphQLResult = await executeGraphQLQuery(username, password); + expectGraphQL404(graphQLResult); + + const graphQLIResult = await executeGraphIQLRequest(username, password); + expectGraphIQL404(graphQLIResult); + } finally { + await security.role.delete(roleName); + await security.user.delete(username); + } + }); + + describe('spaces', () => { + // the following tests create a user_1 which has infrastructure read access to space_1, logs read access to space_2 and dashboard all access to space_3 + const space1Id = 'space_1'; + const space2Id = 'space_2'; + const space3Id = 'space_3'; + + const roleName = 'user_1'; + const username = 'user_1'; + const password = 'user_1-password'; + + before(async () => { + await spaces.create({ + id: space1Id, + name: space1Id, + disabledFeatures: [], + }); + await spaces.create({ + id: space2Id, + name: space2Id, + disabledFeatures: [], + }); + await spaces.create({ + id: space3Id, + name: space3Id, + disabledFeatures: [], + }); + await security.role.create(roleName, { + elasticsearch: { + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + }, + kibana: [ + { + feature: { + infrastructure: ['read'], + }, + spaces: [space1Id], + }, + { + feature: { + logs: ['read'], + }, + spaces: [space2Id], + }, + { + feature: { + dashboard: ['all'], + }, + spaces: [space3Id], + }, + ], + }); + await security.user.create(username, { + password, + roles: [roleName], + }); + }); + + after(async () => { + await spaces.delete(space1Id); + await spaces.delete(space2Id); + await spaces.delete(space3Id); + await security.role.delete(roleName); + await security.user.delete(username); + }); + + it('user_1 can access APIs in space_1', async () => { + const graphQLResult = await executeGraphQLQuery(username, password, space1Id); + expectGraphQLResponse(graphQLResult); + + const graphQLIResult = await executeGraphIQLRequest(username, password, space1Id); + expectGraphIQLResponse(graphQLIResult); + }); + + it(`user_1 can access APIs in space_2`, async () => { + const graphQLResult = await executeGraphQLQuery(username, password, space2Id); + expectGraphQLResponse(graphQLResult); + + const graphQLIResult = await executeGraphIQLRequest(username, password, space2Id); + expectGraphIQLResponse(graphQLIResult); + }); + + it(`user_1 can't access APIs in space_3`, async () => { + const graphQLResult = await executeGraphQLQuery(username, password, space3Id); + expectGraphQL404(graphQLResult); + + const graphQLIResult = await executeGraphIQLRequest(username, password, space3Id); + expectGraphIQL404(graphQLIResult); + }); + }); + }); +}; + +// tslint:disable-next-line no-default-export +export default featureControlsTests; diff --git a/x-pack/test/api_integration/apis/infra/index.js b/x-pack/test/api_integration/apis/infra/index.js index a4c2faed5f5ce..c959facced06a 100644 --- a/x-pack/test/api_integration/apis/infra/index.js +++ b/x-pack/test/api_integration/apis/infra/index.js @@ -14,5 +14,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./sources')); loadTestFile(require.resolve('./waffle')); loadTestFile(require.resolve('./log_item')); + loadTestFile(require.resolve('./feature_controls')); }); } diff --git a/x-pack/test/api_integration/apis/infra/types.ts b/x-pack/test/api_integration/apis/infra/types.ts index 932bef5952698..585f10117b6ce 100644 --- a/x-pack/test/api_integration/apis/infra/types.ts +++ b/x-pack/test/api_integration/apis/infra/types.ts @@ -12,10 +12,19 @@ export interface EsArchiver { unload(name: string): void; } +interface InfraOpsGraphQLClientFactoryOptions { + username: string; + password: string; + basePath: string; +} + export interface KbnTestProviderOptions { getService(name: string): any; getService(name: 'esArchiver'): EsArchiver; getService(name: 'infraOpsGraphQLClient'): ApolloClient; + getService( + name: 'infraOpsGraphQLClientFactory' + ): (options: InfraOpsGraphQLClientFactoryOptions) => ApolloClient; } export type KbnTestProvider = (options: KbnTestProviderOptions) => void; diff --git a/x-pack/test/api_integration/config.js b/x-pack/test/api_integration/config.js index 301464f84f1b3..a9da26722c823 100644 --- a/x-pack/test/api_integration/config.js +++ b/x-pack/test/api_integration/config.js @@ -9,7 +9,8 @@ import { EsSupertestWithoutAuthProvider, SupertestWithoutAuthProvider, UsageAPIProvider, - InfraOpsGraphQLProvider + InfraOpsGraphQLClientProvider, + InfraOpsGraphQLClientFactoryProvider, } from './services'; import { @@ -31,7 +32,8 @@ export default async function ({ readConfigFile }) { esSupertest: kibanaAPITestsConfig.get('services.esSupertest'), supertestWithoutAuth: SupertestWithoutAuthProvider, esSupertestWithoutAuth: EsSupertestWithoutAuthProvider, - infraOpsGraphQLClient: InfraOpsGraphQLProvider, + infraOpsGraphQLClient: InfraOpsGraphQLClientProvider, + infraOpsGraphQLClientFactory: InfraOpsGraphQLClientFactoryProvider, es: EsProvider, esArchiver: kibanaCommonConfig.get('services.esArchiver'), usageAPI: UsageAPIProvider, diff --git a/x-pack/test/api_integration/services/index.js b/x-pack/test/api_integration/services/index.js index 96d349f848629..87325c3b4ad91 100644 --- a/x-pack/test/api_integration/services/index.js +++ b/x-pack/test/api_integration/services/index.js @@ -8,4 +8,4 @@ export { EsProvider } from './es'; export { EsSupertestWithoutAuthProvider } from './es_supertest_without_auth'; export { SupertestWithoutAuthProvider } from './supertest_without_auth'; export { UsageAPIProvider } from './usage_api'; -export { InfraOpsGraphQLProvider } from './infraops_graphql_client'; +export { InfraOpsGraphQLClientProvider, InfraOpsGraphQLClientFactoryProvider } from './infraops_graphql_client'; diff --git a/x-pack/test/api_integration/services/infraops_graphql_client.js b/x-pack/test/api_integration/services/infraops_graphql_client.js index 81cdc442f4fbd..2af5a58ac88c8 100644 --- a/x-pack/test/api_integration/services/infraops_graphql_client.js +++ b/x-pack/test/api_integration/services/infraops_graphql_client.js @@ -9,26 +9,46 @@ import fetch from 'node-fetch'; import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; +import { setContext } from 'apollo-link-context'; import introspectionQueryResultData from '../../../plugins/infra/public/graphql/introspection.json'; -export function InfraOpsGraphQLProvider({ getService }) { +export function InfraOpsGraphQLClientProvider({ getService }) { + return new InfraOpsGraphQLClientFactoryProvider({ getService })(); +} + +export function InfraOpsGraphQLClientFactoryProvider({ getService }) { const config = getService('config'); - const kbnURL = formatUrl(config.get('servers.kibana')); + const [superUsername, superPassword] = config.get('servers.elasticsearch.auth').split(':'); - return new ApolloClient({ - cache: new InMemoryCache({ - fragmentMatcher: new IntrospectionFragmentMatcher({ - introspectionQueryResultData, - }), - }), - link: new HttpLink({ + return function ({ username = superUsername, password = superPassword, basePath = null } = {}) { + const kbnURLWithoutAuth = formatUrl({ ...config.get('servers.kibana'), auth: false }); + + const httpLink = new HttpLink({ credentials: 'same-origin', fetch, headers: { 'kbn-xsrf': 'xxx', }, - uri: `${kbnURL}/api/infra/graphql`, - }), - }); + uri: `${kbnURLWithoutAuth}${basePath || ''}/api/infra/graphql`, + }); + + const authLink = setContext((_, { headers }) => { + return { + headers: { + ...headers, + authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`, + } + }; + }); + + return new ApolloClient({ + cache: new InMemoryCache({ + fragmentMatcher: new IntrospectionFragmentMatcher({ + introspectionQueryResultData, + }), + }), + link: authLink.concat(httpLink), + }); + }; } diff --git a/yarn.lock b/yarn.lock index 36a5f5f2be2a2..c671d80cc371c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3207,6 +3207,13 @@ apollo-client@^2.3.8: optionalDependencies: "@types/async" "2.0.49" +apollo-link-context@^1.0.14: + version "1.0.14" + resolved "https://registry.yarnpkg.com/apollo-link-context/-/apollo-link-context-1.0.14.tgz#6265eef49bedadddbbcff4026d04cd351094cd6c" + integrity sha512-l6SIN7Fwqhgg5C5eA8xSrt8gulHBmYTE3J4z5/Q2hP/8Kok0rQ/z5q3uy42/hkdYlnaktOvpz+ZIwEFzcXwujQ== + dependencies: + apollo-link "^1.2.8" + apollo-link-dedup@^1.0.0: version "1.0.9" resolved "https://registry.yarnpkg.com/apollo-link-dedup/-/apollo-link-dedup-1.0.9.tgz#3c4e4af88ef027cbddfdb857c043fd0574051dad" @@ -3252,6 +3259,13 @@ apollo-link@^1.0.0, apollo-link@^1.2.2, apollo-link@^1.2.3: apollo-utilities "^1.0.0" zen-observable-ts "^0.8.10" +apollo-link@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.8.tgz#0f252adefd5047ac1a9f35ba9439d216587dcd84" + integrity sha512-lfzGRxhK9RmiH3HPFi7TIEBhhDY9M5a2ZDnllcfy5QDk7cCQHQ1WQArcw1FK0g1B+mV4Kl72DSrlvZHZJEolrA== + dependencies: + zen-observable-ts "^0.8.15" + apollo-server-core@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-1.3.6.tgz#08636243c2de56fa8c267d68dd602cb1fbd323e3" @@ -25646,6 +25660,13 @@ zen-observable-ts@^0.8.10: dependencies: zen-observable "^0.8.0" +zen-observable-ts@^0.8.15: + version "0.8.15" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.15.tgz#6cf7df6aa619076e4af2f707ccf8a6290d26699b" + integrity sha512-sXKPWiw6JszNEkRv5dQ+lQCttyjHM2Iks74QU5NP8mMPS/NrzTlHDr780gf/wOBqmHkPO6NCLMlsa+fAQ8VE8w== + dependencies: + zen-observable "^0.8.0" + zen-observable@^0.8.0: version "0.8.8" resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.8.tgz#1ea93995bf098754a58215a1e0a7309e5749ec42" From 8e49558ed2fd11be886fd653018dbf7c69c9bc6f Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 5 Mar 2019 11:04:25 -0800 Subject: [PATCH 30/34] Updating privileges list --- x-pack/test/api_integration/apis/security/privileges.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 0d6e50a2f0902..23822bb0b60f5 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -575,6 +575,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { all: [ 'login:', `version:${version}`, + 'api:infra/graphql', 'app:infra', 'app:kibana', 'ui:catalogue/infraops', @@ -599,6 +600,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { read: [ 'login:', `version:${version}`, + 'api:infra/graphql', 'app:infra', 'app:kibana', 'ui:catalogue/infraops', @@ -618,6 +620,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { all: [ 'login:', `version:${version}`, + 'api:infra/graphql', 'app:infra', 'app:kibana', 'ui:catalogue/infralogging', @@ -642,6 +645,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { read: [ 'login:', `version:${version}`, + 'api:infra/graphql', 'app:infra', 'app:kibana', 'ui:catalogue/infralogging', @@ -842,6 +846,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'ui:savedObjectsManagement/canvas-workpad/delete', 'ui:savedObjectsManagement/canvas-workpad/edit', 'ui:canvas/save', + 'api:infra/graphql', 'app:infra', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', @@ -950,6 +955,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:canvas', 'ui:catalogue/canvas', 'ui:navLinks/canvas', + 'api:infra/graphql', 'app:infra', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', @@ -1122,6 +1128,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'ui:savedObjectsManagement/canvas-workpad/delete', 'ui:savedObjectsManagement/canvas-workpad/edit', 'ui:canvas/save', + 'api:infra/graphql', 'app:infra', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', @@ -1230,6 +1237,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'app:canvas', 'ui:catalogue/canvas', 'ui:navLinks/canvas', + 'api:infra/graphql', 'app:infra', 'ui:catalogue/infraops', 'ui:navLinks/infra:home', From 20cd1287ca5059c5586103cf3d186d53a2008a7d Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 7 Mar 2019 11:53:57 -0800 Subject: [PATCH 31/34] Using the introspection query --- .../apis/infra/feature_controls.ts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/x-pack/test/api_integration/apis/infra/feature_controls.ts b/x-pack/test/api_integration/apis/infra/feature_controls.ts index 370b758bf1200..f4971aea22bdc 100644 --- a/x-pack/test/api_integration/apis/infra/feature_controls.ts +++ b/x-pack/test/api_integration/apis/infra/feature_controls.ts @@ -5,11 +5,20 @@ */ import expect from 'expect.js'; +import gql from 'graphql-tag'; import { SecurityService, SpacesService } from 'x-pack/test/common/services'; -import { metadataQuery } from '../../../../plugins/infra/public/containers/metadata/metadata.gql_query'; -import { MetadataQuery } from '../../../../plugins/infra/public/graphql/types'; import { KbnTestProvider } from './types'; +const introspectionQuery = gql` + query Schema { + __schema { + queryType { + name + } + } + } +`; + // tslint:disable:no-default-export const featureControlsTests: KbnTestProvider = ({ getService }) => { const supertest = getService('supertestWithoutAuth'); @@ -44,12 +53,7 @@ const featureControlsTests: KbnTestProvider = ({ getService }) => { const executeGraphQLQuery = async (username: string, password: string, spaceId?: string) => { const queryOptions = { - query: metadataQuery, - variables: { - sourceId: 'default', - nodeId: 'demo-stack-mysql-01', - nodeType: 'host', - }, + query: introspectionQuery, }; const basePath = spaceId ? `/s/${spaceId}` : ''; @@ -58,7 +62,7 @@ const featureControlsTests: KbnTestProvider = ({ getService }) => { let error; let response; try { - response = await client.query(queryOptions); + response = await client.query(queryOptions); } catch (err) { error = err; } From c41d5b33a1d5d3fcfbde90509b91064fe9f2474d Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 7 Mar 2019 11:58:41 -0800 Subject: [PATCH 32/34] No longer using apollo context library, rephrasing test descriptions --- x-pack/package.json | 1 - .../apis/infra/feature_controls.ts | 6 +++--- .../services/infraops_graphql_client.js | 13 ++---------- yarn.lock | 21 ------------------- 4 files changed, 5 insertions(+), 36 deletions(-) diff --git a/x-pack/package.json b/x-pack/package.json index 7375f853f5651..b7cdaf19a4062 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -70,7 +70,6 @@ "abab": "^1.0.4", "ansi-colors": "^3.0.5", "ansicolors": "0.3.2", - "apollo-link-context": "^1.0.14", "axios": "^0.18.0", "babel-jest": "^23.6.0", "babel-plugin-inline-react-svg": "^0.5.4", diff --git a/x-pack/test/api_integration/apis/infra/feature_controls.ts b/x-pack/test/api_integration/apis/infra/feature_controls.ts index f4971aea22bdc..7a3d62b30d43e 100644 --- a/x-pack/test/api_integration/apis/infra/feature_controls.ts +++ b/x-pack/test/api_integration/apis/infra/feature_controls.ts @@ -83,7 +83,7 @@ const featureControlsTests: KbnTestProvider = ({ getService }) => { }; describe('feature controls', () => { - it(`APIs can't be accessed by logstash-* read privileges role`, async () => { + it(`APIs can't be accessed by user with logstash-* "read" privileges`, async () => { const username = 'logstash_read'; const roleName = 'logstash_read'; const password = `${username}-password`; @@ -116,7 +116,7 @@ const featureControlsTests: KbnTestProvider = ({ getService }) => { } }); - it('APIs can be accessed global all with logstash-* read privileges role', async () => { + it('APIs can be accessed user with global "all" and logstash-* "read" privileges', async () => { const username = 'global_all'; const roleName = 'global_all'; const password = `${username}-password`; @@ -156,7 +156,7 @@ const featureControlsTests: KbnTestProvider = ({ getService }) => { }); // this could be any role which doesn't have access to the infra feature - it(`APIs can't be accessed by dashboard all with logstash-* read privileges role`, async () => { + it(`APIs can't be accessed by user with dashboard "all" and logstash-* "read" privileges`, async () => { const username = 'dashboard_all'; const roleName = 'dashboard_all'; const password = `${username}-password`; diff --git a/x-pack/test/api_integration/services/infraops_graphql_client.js b/x-pack/test/api_integration/services/infraops_graphql_client.js index 2af5a58ac88c8..52f064ee9e195 100644 --- a/x-pack/test/api_integration/services/infraops_graphql_client.js +++ b/x-pack/test/api_integration/services/infraops_graphql_client.js @@ -9,7 +9,6 @@ import fetch from 'node-fetch'; import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; -import { setContext } from 'apollo-link-context'; import introspectionQueryResultData from '../../../plugins/infra/public/graphql/introspection.json'; @@ -29,26 +28,18 @@ export function InfraOpsGraphQLClientFactoryProvider({ getService }) { fetch, headers: { 'kbn-xsrf': 'xxx', + authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`, }, uri: `${kbnURLWithoutAuth}${basePath || ''}/api/infra/graphql`, }); - const authLink = setContext((_, { headers }) => { - return { - headers: { - ...headers, - authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`, - } - }; - }); - return new ApolloClient({ cache: new InMemoryCache({ fragmentMatcher: new IntrospectionFragmentMatcher({ introspectionQueryResultData, }), }), - link: authLink.concat(httpLink), + link: httpLink, }); }; } diff --git a/yarn.lock b/yarn.lock index c671d80cc371c..36a5f5f2be2a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3207,13 +3207,6 @@ apollo-client@^2.3.8: optionalDependencies: "@types/async" "2.0.49" -apollo-link-context@^1.0.14: - version "1.0.14" - resolved "https://registry.yarnpkg.com/apollo-link-context/-/apollo-link-context-1.0.14.tgz#6265eef49bedadddbbcff4026d04cd351094cd6c" - integrity sha512-l6SIN7Fwqhgg5C5eA8xSrt8gulHBmYTE3J4z5/Q2hP/8Kok0rQ/z5q3uy42/hkdYlnaktOvpz+ZIwEFzcXwujQ== - dependencies: - apollo-link "^1.2.8" - apollo-link-dedup@^1.0.0: version "1.0.9" resolved "https://registry.yarnpkg.com/apollo-link-dedup/-/apollo-link-dedup-1.0.9.tgz#3c4e4af88ef027cbddfdb857c043fd0574051dad" @@ -3259,13 +3252,6 @@ apollo-link@^1.0.0, apollo-link@^1.2.2, apollo-link@^1.2.3: apollo-utilities "^1.0.0" zen-observable-ts "^0.8.10" -apollo-link@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.8.tgz#0f252adefd5047ac1a9f35ba9439d216587dcd84" - integrity sha512-lfzGRxhK9RmiH3HPFi7TIEBhhDY9M5a2ZDnllcfy5QDk7cCQHQ1WQArcw1FK0g1B+mV4Kl72DSrlvZHZJEolrA== - dependencies: - zen-observable-ts "^0.8.15" - apollo-server-core@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-1.3.6.tgz#08636243c2de56fa8c267d68dd602cb1fbd323e3" @@ -25660,13 +25646,6 @@ zen-observable-ts@^0.8.10: dependencies: zen-observable "^0.8.0" -zen-observable-ts@^0.8.15: - version "0.8.15" - resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.15.tgz#6cf7df6aa619076e4af2f707ccf8a6290d26699b" - integrity sha512-sXKPWiw6JszNEkRv5dQ+lQCttyjHM2Iks74QU5NP8mMPS/NrzTlHDr780gf/wOBqmHkPO6NCLMlsa+fAQ8VE8w== - dependencies: - zen-observable "^0.8.0" - zen-observable@^0.8.0: version "0.8.8" resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.8.tgz#1ea93995bf098754a58215a1e0a7309e5749ec42" From 92065666fd2cabac0a8951655c4556775029992d Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 7 Mar 2019 13:01:47 -0800 Subject: [PATCH 33/34] Fixing issue introduced by merge conflict, I forgot a } --- .../plugins/infra/public/pages/home/index.tsx | 210 +++++++++--------- 1 file changed, 107 insertions(+), 103 deletions(-) diff --git a/x-pack/plugins/infra/public/pages/home/index.tsx b/x-pack/plugins/infra/public/pages/home/index.tsx index 7f058b87bd2a8..fd5ce93661575 100644 --- a/x-pack/plugins/infra/public/pages/home/index.tsx +++ b/x-pack/plugins/infra/public/pages/home/index.tsx @@ -39,110 +39,114 @@ export const HomePage = injectUICapabilities( public render() { const { intl, uiCapabilities } = this.props; - return ( - - - -

- - - {({ - derivedIndexPattern, - hasFailed, - isLoading, - lastFailureMessage, - load, - metricIndicesExist, - }) => - isLoading ? ( - - ) : metricIndicesExist ? ( - <> - - - - - - - ) : hasFailed ? ( - - ) : ( - - {({ basePath }) => ( - - - - {intl.formatMessage({ - id: 'xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel', - defaultMessage: 'View setup instructions', - })} - - - {uiCapabilities.infrastructure.configureSource ? ( - - - {({ enable }) => ( - - {intl.formatMessage({ - id: 'xpack.infra.configureSourceActionLabel', - defaultMessage: 'Change source configuration', - })} - - )} - - - ) : null + return ( + + + +
+ + + {({ + derivedIndexPattern, + hasFailed, + isLoading, + lastFailureMessage, + load, + metricIndicesExist, + }) => + isLoading ? ( + + ) : metricIndicesExist ? ( + <> + + + + + + + ) : hasFailed ? ( + + ) : ( + + {({ basePath }) => ( + + + + {intl.formatMessage({ + id: + 'xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel', + defaultMessage: 'View setup instructions', + })} + + + {uiCapabilities.infrastructure.configureSource ? ( + + + {({ enable }) => ( + + {intl.formatMessage({ + id: 'xpack.infra.configureSourceActionLabel', + defaultMessage: 'Change source configuration', + })} + + )} + + + ) : null} + } - - - } - data-test-subj="noMetricsIndicesPrompt" - /> - )} - - ) - } - - - ); + data-test-subj="noMetricsIndicesPrompt" + /> + )} + + ) + } + + + ); + } } ) ); From 6371bb6bf950bb692c42f45db295b25c218a4037 Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 7 Mar 2019 15:43:49 -0800 Subject: [PATCH 34/34] Putting back missplaced data test subj --- x-pack/plugins/infra/public/pages/logs/logs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/pages/logs/logs.tsx b/x-pack/plugins/infra/public/pages/logs/logs.tsx index 3e2be0879363e..19c5496d91778 100644 --- a/x-pack/plugins/infra/public/pages/logs/logs.tsx +++ b/x-pack/plugins/infra/public/pages/logs/logs.tsx @@ -48,7 +48,7 @@ export const LogsPage = injectUICapabilities( return ( - +