diff --git a/src/ui/public/chrome/index.d.ts b/src/ui/public/chrome/index.d.ts index 6b7835a26f90b..e4ce8a6bb5b15 100644 --- a/src/ui/public/chrome/index.d.ts +++ b/src/ui/public/chrome/index.d.ts @@ -28,6 +28,7 @@ declare class Chrome { public getXsrfToken(): string; public getKibanaVersion(): string; public getUiSettingsClient(): any; + public setVisible(visible: boolean): any; } declare const chrome: Chrome; diff --git a/x-pack/package.json b/x-pack/package.json index 8c5b5a9cd4581..248e6f7ead6fc 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -27,6 +27,7 @@ "@kbn/test": "link:../packages/kbn-test", "@types/expect.js": "^0.3.29", "@types/jest": "^23.3.1", + "@types/joi": "^10.4.4", "@types/mocha": "^5.2.5", "@types/pngjs": "^3.3.1", "@types/supertest": "^2.0.5", diff --git a/x-pack/plugins/spaces/common/is_reserved_space.test.js b/x-pack/plugins/spaces/common/is_reserved_space.test.ts similarity index 71% rename from x-pack/plugins/spaces/common/is_reserved_space.test.js rename to x-pack/plugins/spaces/common/is_reserved_space.test.ts index a2d610199ba38..7c0bfb74b86eb 100644 --- a/x-pack/plugins/spaces/common/is_reserved_space.test.js +++ b/x-pack/plugins/spaces/common/is_reserved_space.test.ts @@ -5,21 +5,28 @@ */ import { isReservedSpace } from './is_reserved_space'; +import { Space } from './model/space'; test('it returns true for reserved spaces', () => { - const space = { - _reserved: true + const space: Space = { + id: '', + name: '', + _reserved: true, }; expect(isReservedSpace(space)).toEqual(true); }); test('it returns false for non-reserved spaces', () => { - const space = {}; + const space: Space = { + id: '', + name: '', + }; expect(isReservedSpace(space)).toEqual(false); }); -test('it handles empty imput', () => { +test('it handles empty input', () => { + // @ts-ignore expect(isReservedSpace()).toEqual(false); }); diff --git a/x-pack/plugins/spaces/common/is_reserved_space.ts b/x-pack/plugins/spaces/common/is_reserved_space.ts index 40acd7630b66c..788ef80c194ce 100644 --- a/x-pack/plugins/spaces/common/is_reserved_space.ts +++ b/x-pack/plugins/spaces/common/is_reserved_space.ts @@ -13,6 +13,6 @@ import { Space } from './model/space'; * @param space the space * @returns boolean */ -export function isReservedSpace(space: Space | null): boolean { +export function isReservedSpace(space?: Partial | null): boolean { return get(space, '_reserved', false); } diff --git a/x-pack/plugins/spaces/common/space_attributes.test.js b/x-pack/plugins/spaces/common/space_attributes.test.ts similarity index 90% rename from x-pack/plugins/spaces/common/space_attributes.test.js rename to x-pack/plugins/spaces/common/space_attributes.test.ts index 8ed03ab21c413..c999dde275643 100644 --- a/x-pack/plugins/spaces/common/space_attributes.test.js +++ b/x-pack/plugins/spaces/common/space_attributes.test.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getSpaceInitials, getSpaceColor } from "./space_attributes"; +import { getSpaceColor, getSpaceInitials } from './space_attributes'; -describe("getSpaceColor", () => { +describe('getSpaceColor', () => { test('uses color on the space, when provided', () => { const space = { name: 'Foo', - color: '#aabbcc' + color: '#aabbcc', }; expect(getSpaceColor(space)).toEqual('#aabbcc'); @@ -37,11 +37,11 @@ describe("getSpaceColor", () => { }); }); -describe("getSpaceInitials", () => { +describe('getSpaceInitials', () => { test('uses initials on the space, when provided', () => { const space = { name: 'Foo', - initials: 'JK' + initials: 'JK', }; expect(getSpaceInitials(space)).toEqual('JK'); diff --git a/x-pack/plugins/spaces/index.js b/x-pack/plugins/spaces/index.js deleted file mode 100644 index 7164854a11e2b..0000000000000 --- a/x-pack/plugins/spaces/index.js +++ /dev/null @@ -1,139 +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 { resolve } from 'path'; -import { validateConfig } from './server/lib/validate_config'; -import { checkLicense } from './server/lib/check_license'; -import { initPublicSpacesApi } from './server/routes/api/public'; -import { initPrivateApis } from './server/routes/api/v1'; -import { initSpacesRequestInterceptors } from './server/lib/space_request_interceptors'; -import { createDefaultSpace } from './server/lib/create_default_space'; -import { createSpacesService } from './server/lib/create_spaces_service'; -import { getActiveSpace } from './server/lib/get_active_space'; -import { getSpacesUsageCollector } from './server/lib/get_spaces_usage_collector'; -import { createSpacesTutorialContextFactory } from './server/lib/spaces_tutorial_context_factory'; -import { wrapError } from './server/lib/errors'; -import mappings from './mappings.json'; -import { spacesSavedObjectsClientWrapperFactory } from './server/lib/saved_objects_client/saved_objects_client_wrapper_factory'; -import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize'; -import { registerUserProfileCapabilityFactory } from '../xpack_main/server/lib/user_profile_registry'; -import { SpacesClient } from './server/lib/spaces_client'; -import { SpacesAuditLogger } from './server/lib/audit_logger'; -import { AuditLogger } from '../../server/lib/audit_logger'; - -export const spaces = (kibana) => new kibana.Plugin({ - id: 'spaces', - configPrefix: 'xpack.spaces', - publicDir: resolve(__dirname, 'public'), - require: ['kibana', 'elasticsearch', 'xpack_main'], - - config(Joi) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - - uiExports: { - chromeNavControls: ['plugins/spaces/views/nav_control'], - managementSections: ['plugins/spaces/views/management'], - apps: [{ - id: 'space_selector', - title: 'Spaces', - main: 'plugins/spaces/views/space_selector', - url: 'space_selector', - hidden: true, - }], - hacks: [], - mappings, - savedObjectsSchema: { - space: { - isNamespaceAgnostic: true, - }, - }, - home: ['plugins/spaces/register_feature'], - injectDefaultVars: function (server) { - return { - spaces: [], - activeSpace: null, - spaceSelectorURL: server.config().get('server.basePath') || '/', - }; - }, - replaceInjectedVars: async function (vars, request, server) { - const spacesClient = server.plugins.spaces.spacesClient.getScopedClient(request); - try { - vars.activeSpace = { - valid: true, - space: await getActiveSpace(spacesClient, request.getBasePath(), server.config().get('server.basePath')) - }; - } catch (e) { - vars.activeSpace = { - valid: false, - error: wrapError(e).output.payload - }; - } - return vars; - } - }, - - async init(server) { - const thisPlugin = this; - const xpackMainPlugin = server.plugins.xpack_main; - - watchStatusAndLicenseToInitialize(xpackMainPlugin, thisPlugin, async () => { - await createDefaultSpace(server); - }); - - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info.feature(thisPlugin.id).registerLicenseCheckResultsGenerator(checkLicense); - - const config = server.config(); - validateConfig(config, message => server.log(['spaces', 'warning'], message)); - - const spacesService = createSpacesService(server); - server.expose('getSpaceId', (request) => spacesService.getSpaceId(request)); - - const spacesAuditLogger = new SpacesAuditLogger(config, new AuditLogger(server, 'spaces')); - - server.expose('spacesClient', { - getScopedClient: (request) => { - const adminCluster = server.plugins.elasticsearch.getCluster('admin'); - const { callWithRequest, callWithInternalUser } = adminCluster; - const callCluster = (...args) => callWithRequest(request, ...args); - const { savedObjects } = server; - const internalRepository = savedObjects.getSavedObjectsRepository(callWithInternalUser); - const callWithRequestRepository = savedObjects.getSavedObjectsRepository(callCluster); - const authorization = server.plugins.security ? server.plugins.security.authorization : null; - return new SpacesClient(spacesAuditLogger, authorization, callWithRequestRepository, internalRepository, request); - } - }); - - const { addScopedSavedObjectsClientWrapperFactory, types } = server.savedObjects; - addScopedSavedObjectsClientWrapperFactory(Number.MAX_VALUE, - spacesSavedObjectsClientWrapperFactory(spacesService, types) - ); - - server.addScopedTutorialContextFactory( - createSpacesTutorialContextFactory(spacesService) - ); - - initPrivateApis(server); - initPublicSpacesApi(server); - - initSpacesRequestInterceptors(server); - - registerUserProfileCapabilityFactory(async (request) => { - const spacesClient = server.plugins.spaces.spacesClient.getScopedClient(request); - - return { - manageSpaces: await spacesClient.canEnumerateSpaces(), - }; - }); - - // Register a function with server to manage the collection of usage stats - server.usage.collectorSet.register(getSpacesUsageCollector(server)); - } -}); diff --git a/x-pack/plugins/spaces/index.ts b/x-pack/plugins/spaces/index.ts new file mode 100644 index 0000000000000..157bc2a010895 --- /dev/null +++ b/x-pack/plugins/spaces/index.ts @@ -0,0 +1,155 @@ +/* + * 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 { resolve } from 'path'; +// @ts-ignore +import { AuditLogger } from '../../server/lib/audit_logger'; +// @ts-ignore +import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize'; +import { registerUserProfileCapabilityFactory } from '../xpack_main/server/lib/user_profile_registry'; +import mappings from './mappings.json'; +import { SpacesAuditLogger } from './server/lib/audit_logger'; +import { checkLicense } from './server/lib/check_license'; +import { createDefaultSpace } from './server/lib/create_default_space'; +import { createSpacesService } from './server/lib/create_spaces_service'; +import { wrapError } from './server/lib/errors'; +import { getActiveSpace } from './server/lib/get_active_space'; +import { getSpacesUsageCollector } from './server/lib/get_spaces_usage_collector'; +import { spacesSavedObjectsClientWrapperFactory } from './server/lib/saved_objects_client/saved_objects_client_wrapper_factory'; +import { initSpacesRequestInterceptors } from './server/lib/space_request_interceptors'; +import { SpacesClient } from './server/lib/spaces_client'; +import { createSpacesTutorialContextFactory } from './server/lib/spaces_tutorial_context_factory'; +import { initPublicSpacesApi } from './server/routes/api/public'; +import { initPrivateApis } from './server/routes/api/v1'; + +export const spaces = (kibana: any) => + new kibana.Plugin({ + id: 'spaces', + configPrefix: 'xpack.spaces', + publicDir: resolve(__dirname, 'public'), + require: ['kibana', 'elasticsearch', 'xpack_main'], + + config(Joi: any) { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + + uiExports: { + chromeNavControls: ['plugins/spaces/views/nav_control'], + managementSections: ['plugins/spaces/views/management'], + apps: [ + { + id: 'space_selector', + title: 'Spaces', + main: 'plugins/spaces/views/space_selector', + url: 'space_selector', + hidden: true, + }, + ], + hacks: [], + mappings, + savedObjectsSchema: { + space: { + isNamespaceAgnostic: true, + }, + }, + home: ['plugins/spaces/register_feature'], + injectDefaultVars(server: any) { + return { + spaces: [], + activeSpace: null, + spaceSelectorURL: server.config().get('server.basePath') || '/', + }; + }, + async replaceInjectedVars(vars: any, request: any, server: any) { + const spacesClient = server.plugins.spaces.spacesClient.getScopedClient(request); + try { + vars.activeSpace = { + valid: true, + space: await getActiveSpace( + spacesClient, + request.getBasePath(), + server.config().get('server.basePath') + ), + }; + } catch (e) { + vars.activeSpace = { + valid: false, + error: wrapError(e).output.payload, + }; + } + return vars; + }, + }, + + async init(server: any) { + const thisPlugin = this; + const xpackMainPlugin = server.plugins.xpack_main; + + watchStatusAndLicenseToInitialize(xpackMainPlugin, thisPlugin, async () => { + await createDefaultSpace(server); + }); + + // Register a function that is called whenever the xpack info changes, + // to re-compute the license check results for this plugin + xpackMainPlugin.info + .feature(thisPlugin.id) + .registerLicenseCheckResultsGenerator(checkLicense); + + const spacesService = createSpacesService(server); + server.expose('getSpaceId', (request: any) => spacesService.getSpaceId(request)); + + const config = server.config(); + + const spacesAuditLogger = new SpacesAuditLogger(config, new AuditLogger(server, 'spaces')); + + server.expose('spacesClient', { + getScopedClient: (request: any) => { + const adminCluster = server.plugins.elasticsearch.getCluster('admin'); + const { callWithRequest, callWithInternalUser } = adminCluster; + const callCluster = (...args: any[]) => callWithRequest(request, ...args); + const { savedObjects } = server; + const internalRepository = savedObjects.getSavedObjectsRepository(callWithInternalUser); + const callWithRequestRepository = savedObjects.getSavedObjectsRepository(callCluster); + const authorization = server.plugins.security + ? server.plugins.security.authorization + : null; + return new SpacesClient( + spacesAuditLogger, + authorization, + callWithRequestRepository, + internalRepository, + request + ); + }, + }); + + const { addScopedSavedObjectsClientWrapperFactory, types } = server.savedObjects; + addScopedSavedObjectsClientWrapperFactory( + Number.MAX_VALUE, + spacesSavedObjectsClientWrapperFactory(spacesService, types) + ); + + server.addScopedTutorialContextFactory(createSpacesTutorialContextFactory(spacesService)); + + initPrivateApis(server); + initPublicSpacesApi(server); + + initSpacesRequestInterceptors(server); + + registerUserProfileCapabilityFactory(async request => { + const spacesClient = server.plugins.spaces.spacesClient.getScopedClient(request); + + return { + manageSpaces: await spacesClient.canEnumerateSpaces(), + }; + }); + + // Register a function with server to manage the collection of usage stats + server.usage.collectorSet.register(getSpacesUsageCollector(server)); + }, + }); diff --git a/x-pack/plugins/spaces/public/components/__snapshots__/space_avatar.test.js.snap b/x-pack/plugins/spaces/public/components/__snapshots__/space_avatar.test.tsx.snap similarity index 100% rename from x-pack/plugins/spaces/public/components/__snapshots__/space_avatar.test.js.snap rename to x-pack/plugins/spaces/public/components/__snapshots__/space_avatar.test.tsx.snap diff --git a/x-pack/plugins/spaces/public/components/space_avatar.test.js b/x-pack/plugins/spaces/public/components/space_avatar.test.tsx similarity index 85% rename from x-pack/plugins/spaces/public/components/space_avatar.test.js rename to x-pack/plugins/spaces/public/components/space_avatar.test.tsx index d63d3b393940d..47f1b29bd55a1 100644 --- a/x-pack/plugins/spaces/public/components/space_avatar.test.js +++ b/x-pack/plugins/spaces/public/components/space_avatar.test.tsx @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; import { SpaceAvatar } from './space_avatar'; test('renders without crashing', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); diff --git a/x-pack/plugins/spaces/public/components/space_avatar.tsx b/x-pack/plugins/spaces/public/components/space_avatar.tsx index 571cc083cb868..e18459bbd8bf9 100644 --- a/x-pack/plugins/spaces/public/components/space_avatar.tsx +++ b/x-pack/plugins/spaces/public/components/space_avatar.tsx @@ -10,7 +10,7 @@ import { getSpaceColor, getSpaceInitials, MAX_SPACE_INITIALS } from '../../commo import { Space } from '../../common/model/space'; interface Props { - space: Space; + space: Partial; size?: 's' | 'm' | 'l' | 'xl'; className?: string; } diff --git a/x-pack/plugins/spaces/public/images/demo.png b/x-pack/plugins/spaces/public/images/demo.png deleted file mode 100644 index 700526918d4e7..0000000000000 Binary files a/x-pack/plugins/spaces/public/images/demo.png and /dev/null differ diff --git a/x-pack/plugins/spaces/public/register_feature.js b/x-pack/plugins/spaces/public/register_feature.ts similarity index 76% rename from x-pack/plugins/spaces/public/register_feature.js rename to x-pack/plugins/spaces/public/register_feature.ts index 1a02313aa331d..6744590e7d35a 100644 --- a/x-pack/plugins/spaces/public/register_feature.js +++ b/x-pack/plugins/spaces/public/register_feature.ts @@ -4,9 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ - - -import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +import { + FeatureCatalogueCategory, + FeatureCatalogueRegistryProvider, + // @ts-ignore +} from 'ui/registry/feature_catalogue'; import { SPACES_FEATURE_DESCRIPTION } from './lib/constants'; FeatureCatalogueRegistryProvider.register(() => { @@ -17,6 +19,6 @@ FeatureCatalogueRegistryProvider.register(() => { icon: 'spacesApp', path: '/app/kibana#/management/spaces/list', showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN + category: FeatureCatalogueCategory.ADMIN, }; }); diff --git a/x-pack/plugins/spaces/public/views/components/space_card.test.js b/x-pack/plugins/spaces/public/views/components/space_card.test.tsx similarity index 77% rename from x-pack/plugins/spaces/public/views/components/space_card.test.js rename to x-pack/plugins/spaces/public/views/components/space_card.test.tsx index f79c74201eeee..26f8a226315b8 100644 --- a/x-pack/plugins/spaces/public/views/components/space_card.test.js +++ b/x-pack/plugins/spaces/public/views/components/space_card.test.tsx @@ -4,23 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ +import { mount, shallow } from 'enzyme'; import React from 'react'; -import { shallow, mount } from 'enzyme'; import { SpaceCard } from './space_card'; test('it renders without crashing', () => { const space = { + id: '', name: 'space name', - description: 'space description' + description: 'space description', }; - shallow(); + shallow(); }); test('it is clickable', () => { const space = { + id: '', name: 'space name', - description: 'space description' + description: 'space description', }; const clickHandler = jest.fn(); diff --git a/x-pack/plugins/spaces/public/views/components/space_cards.test.js b/x-pack/plugins/spaces/public/views/components/space_cards.test.tsx similarity index 93% rename from x-pack/plugins/spaces/public/views/components/space_cards.test.js rename to x-pack/plugins/spaces/public/views/components/space_cards.test.tsx index 0a7fdd79d2059..591f8c1507b7a 100644 --- a/x-pack/plugins/spaces/public/views/components/space_cards.test.js +++ b/x-pack/plugins/spaces/public/views/components/space_cards.test.tsx @@ -4,15 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; import { SpaceCards } from './space_cards'; test('it renders without crashing', () => { const space = { id: 'space-id', name: 'space name', - description: 'space description' + description: 'space description', }; shallow(); diff --git a/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx b/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx index 0810243a1d035..28515ba71593d 100644 --- a/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx +++ b/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx @@ -13,6 +13,7 @@ import { EuiOverlayMask, EuiText, } from '@elastic/eui'; +import { SpacesNavState } from 'plugins/spaces/views/nav_control'; import React, { ChangeEvent, Component } from 'react'; import { Space } from '../../../../common/model/space'; import { SpacesManager } from '../../../lib'; @@ -20,7 +21,7 @@ import { SpacesManager } from '../../../lib'; interface Props { space: Space; spacesManager: SpacesManager; - spacesNavState: any; + spacesNavState: SpacesNavState; onCancel: () => void; onConfirm: () => void; } @@ -116,6 +117,6 @@ export class ConfirmDeleteModal extends Component { }; } -function isDeletingCurrentSpace(space: Space, spacesNavState: any) { +function isDeletingCurrentSpace(space: Space, spacesNavState: SpacesNavState) { return space.id === spacesNavState.getActiveSpace().id; } diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/__snapshots__/customize_space_avatar.test.js.snap b/x-pack/plugins/spaces/public/views/management/edit_space/__snapshots__/customize_space_avatar.test.tsx.snap similarity index 100% rename from x-pack/plugins/spaces/public/views/management/edit_space/__snapshots__/customize_space_avatar.test.js.snap rename to x-pack/plugins/spaces/public/views/management/edit_space/__snapshots__/customize_space_avatar.test.tsx.snap diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/__snapshots__/space_identifier.test.js.snap b/x-pack/plugins/spaces/public/views/management/edit_space/__snapshots__/space_identifier.test.tsx.snap similarity index 100% rename from x-pack/plugins/spaces/public/views/management/edit_space/__snapshots__/space_identifier.test.js.snap rename to x-pack/plugins/spaces/public/views/management/edit_space/__snapshots__/space_identifier.test.tsx.snap diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/customize_space_avatar.test.js b/x-pack/plugins/spaces/public/views/management/edit_space/customize_space_avatar.test.tsx similarity index 60% rename from x-pack/plugins/spaces/public/views/management/edit_space/customize_space_avatar.test.js rename to x-pack/plugins/spaces/public/views/management/edit_space/customize_space_avatar.test.tsx index dfd1c85d19e3e..180ed9b0dfef7 100644 --- a/x-pack/plugins/spaces/public/views/management/edit_space/customize_space_avatar.test.js +++ b/x-pack/plugins/spaces/public/views/management/edit_space/customize_space_avatar.test.tsx @@ -3,24 +3,29 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +// @ts-ignore +import { EuiColorPicker, EuiFieldText, EuiLink } from '@elastic/eui'; +import { mount, shallow } from 'enzyme'; import React from 'react'; -import { shallow, mount } from 'enzyme'; import { CustomizeSpaceAvatar } from './customize_space_avatar'; -import { EuiLink, EuiFieldText, EuiColorPicker } from '@elastic/eui'; + +const space = { + id: '', + name: '', +}; test('renders without crashing', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); test('renders a "customize" link by default', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find(EuiLink)).toHaveLength(1); }); test('shows customization fields when the "customize" link is clicked', () => { - const wrapper = mount(); + const wrapper = mount(); wrapper.find(EuiLink).simulate('click'); expect(wrapper.find(EuiLink)).toHaveLength(0); @@ -29,21 +34,25 @@ test('shows customization fields when the "customize" link is clicked', () => { }); test('invokes onChange callback when avatar is customized', () => { - const space = { - name: "Unit Test Space", - initials: "SP", - color: "#ABCDEF" + const customizedSpace = { + id: '', + name: 'Unit Test Space', + initials: 'SP', + color: '#ABCDEF', }; const changeHandler = jest.fn(); - const wrapper = mount(); + const wrapper = mount(); wrapper.find(EuiLink).simulate('click'); - wrapper.find(EuiFieldText).find('input').simulate('change', { target: { value: 'NV' } }); + wrapper + .find(EuiFieldText) + .find('input') + .simulate('change', { target: { value: 'NV' } }); expect(changeHandler).toHaveBeenCalledWith({ - ...space, - initials: 'NV' + ...customizedSpace, + initials: 'NV', }); -}); \ No newline at end of file +}); diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/customize_space_avatar.js b/x-pack/plugins/spaces/public/views/management/edit_space/customize_space_avatar.tsx similarity index 60% rename from x-pack/plugins/spaces/public/views/management/edit_space/customize_space_avatar.js rename to x-pack/plugins/spaces/public/views/management/edit_space/customize_space_avatar.tsx index 7d6240d6a2c0a..be279536ccd88 100644 --- a/x-pack/plugins/spaces/public/views/management/edit_space/customize_space_avatar.js +++ b/x-pack/plugins/spaces/public/views/management/edit_space/customize_space_avatar.tsx @@ -3,44 +3,43 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { - EuiFlexItem, - EuiColorPicker, - EuiFormRow, - EuiFieldText, - EuiLink, -} from '@elastic/eui'; -import { getSpaceInitials, getSpaceColor } from '../../../../common/space_attributes'; +// @ts-ignore +import { EuiColorPicker, EuiFieldText, EuiFlexItem, EuiFormRow, EuiLink } from '@elastic/eui'; +import React, { ChangeEvent, Component, Fragment } from 'react'; import { MAX_SPACE_INITIALS } from '../../../../common/constants'; +import { Space } from '../../../../common/model/space'; +import { getSpaceColor, getSpaceInitials } from '../../../../common/space_attributes'; -export class CustomizeSpaceAvatar extends Component { - static propTypes = { - space: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, - } +interface Props { + space: Partial; + onChange: (space: Partial) => void; +} + +interface State { + expanded: boolean; + initialsHasFocus: boolean; + pendingInitials?: string | null; +} + +export class CustomizeSpaceAvatar extends Component { + private initialsRef: HTMLInputElement | null = null; - state = { - expanded: false, - initialsHasFocus: false, - pendingInitials: null + constructor(props: Props) { + super(props); + this.state = { + expanded: false, + initialsHasFocus: false, + }; } - render() { + public render() { return this.state.expanded ? this.getCustomizeFields() : this.getCustomizeLink(); } - getCustomizeFields = () => { - const { - space - } = this.props; + public getCustomizeFields = () => { + const { space } = this.props; - const { - initialsHasFocus, - pendingInitials, - } = this.state; + const { initialsHasFocus, pendingInitials } = this.state; return ( @@ -51,7 +50,7 @@ export class CustomizeSpaceAvatar extends Component { name="spaceInitials" // allows input to be cleared or otherwise invalidated while user is editing the initials, // without defaulting to the derived initials provided by `getSpaceInitials` - value={initialsHasFocus ? pendingInitials : getSpaceInitials(space)} + value={initialsHasFocus ? pendingInitials || '' : getSpaceInitials(space)} onChange={this.onInitialsChange} /> @@ -63,9 +62,9 @@ export class CustomizeSpaceAvatar extends Component { ); - } + }; - initialsInputRef = (ref) => { + public initialsInputRef = (ref: HTMLInputElement) => { if (ref) { this.initialsRef = ref; this.initialsRef.addEventListener('focus', this.onInitialsFocus); @@ -77,39 +76,41 @@ export class CustomizeSpaceAvatar extends Component { this.initialsRef = null; } } - } + }; - onInitialsFocus = () => { + public onInitialsFocus = () => { this.setState({ initialsHasFocus: true, - pendingInitials: getSpaceInitials(this.props.space) + pendingInitials: getSpaceInitials(this.props.space), }); - } + }; - onInitialsBlur = () => { + public onInitialsBlur = () => { this.setState({ initialsHasFocus: false, pendingInitials: null, }); - } + }; - getCustomizeLink = () => { + public getCustomizeLink = () => { return ( - Customize + + Customize + ); - } + }; - showFields = () => { + public showFields = () => { this.setState({ - expanded: true + expanded: true, }); - } + }; - onInitialsChange = (e) => { + public onInitialsChange = (e: ChangeEvent) => { const initials = (e.target.value || '').substring(0, MAX_SPACE_INITIALS); this.setState({ @@ -118,14 +119,14 @@ export class CustomizeSpaceAvatar extends Component { this.props.onChange({ ...this.props.space, - initials + initials, }); }; - onColorChange = (color) => { + public onColorChange = (color: string) => { this.props.onChange({ ...this.props.space, - color + color, }); - } + }; } diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx b/x-pack/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx index e255375baf85b..25aea985e01ac 100644 --- a/x-pack/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx +++ b/x-pack/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx @@ -5,7 +5,9 @@ */ import { EuiButton, EuiButtonIcon, EuiButtonIconProps } from '@elastic/eui'; +import { SpacesNavState } from 'plugins/spaces/views/nav_control'; import React, { Component, Fragment } from 'react'; +// @ts-ignore import { toastNotifications } from 'ui/notify'; import { Space } from '../../../../common/model/space'; import { SpacesManager } from '../../../lib/spaces_manager'; @@ -15,7 +17,7 @@ interface Props { style?: 'button' | 'icon'; space: Space; spacesManager: SpacesManager; - spacesNavState: any; + spacesNavState: SpacesNavState; onDelete: () => void; } diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/index.js b/x-pack/plugins/spaces/public/views/management/edit_space/index.ts similarity index 77% rename from x-pack/plugins/spaces/public/views/management/edit_space/index.js rename to x-pack/plugins/spaces/public/views/management/edit_space/index.ts index 0d5429ca5ed55..65018a5e9271c 100644 --- a/x-pack/plugins/spaces/public/views/management/edit_space/index.js +++ b/x-pack/plugins/spaces/public/views/management/edit_space/index.ts @@ -3,5 +3,5 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -export { ManageSpacePage } from './manage_space_page'; \ No newline at end of file +// @ts-ignore +export { ManageSpacePage } from './manage_space_page'; diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.js b/x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx similarity index 55% rename from x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.js rename to x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx index 0ff739e127656..5b1e389335212 100644 --- a/x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.js +++ b/x-pack/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx @@ -4,71 +4,85 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, Component } from 'react'; -import PropTypes from 'prop-types'; import { - EuiTitle, + EuiButton, EuiButtonEmpty, - EuiSpacer, - EuiPage, - EuiPageBody, - EuiPageContent, - EuiForm, - EuiFormRow, EuiFieldText, EuiFlexGroup, EuiFlexItem, - EuiButton, - EuiPageContentBody, + EuiForm, + EuiFormRow, EuiHorizontalRule, EuiLoadingSpinner, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiSpacer, + EuiTitle, } from '@elastic/eui'; +import React, { ChangeEvent, Component, Fragment } from 'react'; -import { DeleteSpacesButton } from './delete_spaces_button'; +import { SpacesNavState } from 'plugins/spaces/views/nav_control'; +import { UserProfile } from 'plugins/xpack_main/services/user_profile'; +// @ts-ignore +import { toastNotifications } from 'ui/notify'; +import { isReservedSpace } from '../../../../common'; +import { Space } from '../../../../common/model/space'; import { SpaceAvatar } from '../../../components'; - -import { Notifier, toastNotifications } from 'ui/notify'; -import { SpaceIdentifier } from './space_identifier'; +import { SpacesManager } from '../../../lib'; +import { UnauthorizedPrompt } from '../components/unauthorized_prompt'; import { toSpaceIdentifier } from '../lib'; +import { SpaceValidator } from '../lib/validate_space'; import { CustomizeSpaceAvatar } from './customize_space_avatar'; -import { isReservedSpace } from '../../../../common'; +import { DeleteSpacesButton } from './delete_spaces_button'; import { ReservedSpaceBadge } from './reserved_space_badge'; -import { SpaceValidator } from '../lib/validate_space'; -import { UnauthorizedPrompt } from '../components/unauthorized_prompt'; +import { SpaceIdentifier } from './space_identifier'; + +interface Props { + spacesManager: SpacesManager; + spaceId?: string; + userProfile: UserProfile; + spacesNavState: SpacesNavState; +} -export class ManageSpacePage extends Component { - state = { - space: {}, - isLoading: true, +interface State { + space: Partial; + isLoading: boolean; + formError?: { + isInvalid: boolean; + error?: string; }; +} + +export class ManageSpacePage extends Component { + private readonly validator: SpaceValidator; - constructor(props) { + constructor(props: Props) { super(props); this.validator = new SpaceValidator({ shouldValidate: false }); + this.state = { + isLoading: true, + space: {}, + }; } - componentDidMount() { - this.notifier = new Notifier({ location: 'Spaces' }); + public componentDidMount() { + const { spaceId, spacesManager } = this.props; - const { - space, + if (spaceId) { spacesManager - } = this.props; - - if (space) { - spacesManager.getSpace(space) - .then(result => { + .getSpace(spaceId) + .then((result: any) => { if (result.data) { this.setState({ space: result.data, - isLoading: false + isLoading: false, }); } }) .catch(error => { - const { - message = '' - } = error.data || {}; + const { message = '' } = error.data || {}; toastNotifications.addDanger(`Error loading space: ${message}`); this.backToSpacesList(); @@ -78,40 +92,39 @@ export class ManageSpacePage extends Component { } } - render() { - + public render() { const content = this.state.isLoading ? this.getLoadingIndicator() : this.getForm(); return ( - - {content} - + {content} ); } - getLoadingIndicator = () => { - return

Loading...

; - } + public getLoadingIndicator = () => { + return ( +
+ {' '} + +

Loading...

+
+
+ ); + }; - getForm = () => { - const { - userProfile - } = this.props; + public getForm = () => { + const { userProfile } = this.props; if (!userProfile.hasCapability('manageSpaces')) { return ; } - const { - name = '', - description = '', - } = this.state.space; + const { name = '', description = '' } = this.state.space; return ( @@ -121,10 +134,7 @@ export class ManageSpacePage extends Component { - + - { - name && ( - - - - - - - - - - - ) - } + {name && ( + + + + + + + + + + + )} - {isReservedSpace(this.state.space) - ? null - : ( - - - - ) - } + {this.state.space && isReservedSpace(this.state.space) ? null : ( + + + + )} {this.getFormButtons()} - ); - } + }; - getFormHeading = () => { + public getFormHeading = () => { return ( -

{this.getTitle()}

+ +

+ {this.getTitle()} +

+
); }; - getTitle = () => { + public getTitle = () => { if (this.editingExistingSpace()) { return `Edit space`; } return `Create space`; }; - getFormButtons = () => { + public getFormButtons = () => { const saveText = this.editingExistingSpace() ? 'Update space' : 'Create space'; return ( - {saveText} + + {saveText} + - - Cancel - + Cancel {this.getActionButton()} ); - } - + }; - getActionButton = () => { - if (this.editingExistingSpace() && !isReservedSpace(this.state.space)) { + public getActionButton = () => { + if (this.state.space && this.editingExistingSpace() && !isReservedSpace(this.state.space)) { return ( { + public onNameChange = (e: ChangeEvent) => { + if (!this.state.space) { + return; + } + const canUpdateId = !this.editingExistingSpace(); - let { - id - } = this.state.space; + let { id } = this.state.space; if (canUpdateId) { id = toSpaceIdentifier(e.target.value); @@ -251,58 +260,57 @@ export class ManageSpacePage extends Component { space: { ...this.state.space, name: e.target.value, - id - } + id, + }, }); }; - onDescriptionChange = (e) => { + public onDescriptionChange = (e: ChangeEvent) => { this.setState({ space: { ...this.state.space, - description: e.target.value - } + description: e.target.value, + }, }); }; - onSpaceIdentifierChange = (e) => { + public onSpaceIdentifierChange = (e: ChangeEvent) => { this.setState({ space: { ...this.state.space, - id: toSpaceIdentifier(e.target.value) - } + id: toSpaceIdentifier(e.target.value), + }, }); }; - onAvatarChange = (space) => { + public onAvatarChange = (space: Partial) => { this.setState({ - space + space, }); - } + }; - saveSpace = () => { + public saveSpace = () => { this.validator.enableValidation(); - const result = this.validator.validateForSave(this.state.space); + const result = this.validator.validateForSave(this.state.space as Space); if (result.isInvalid) { this.setState({ - formError: result + formError: result, }); return; } - this._performSave(); + this.performSave(); }; - _performSave = () => { - const { - name = '', - id = toSpaceIdentifier(name), - description, - initials, - color, - } = this.state.space; + private performSave = () => { + if (!this.state.space) { + return; + } + + const name = this.state.space.name || ''; + const { id = toSpaceIdentifier(name), description, initials, color } = this.state.space; const params = { name, @@ -326,24 +334,15 @@ export class ManageSpacePage extends Component { window.location.hash = `#/management/spaces/list`; }) .catch(error => { - const { - message = '' - } = error.data || {}; + const { message = '' } = error.data || {}; toastNotifications.addDanger(`Error saving space: ${message}`); }); }; - backToSpacesList = () => { + private backToSpacesList = () => { window.location.hash = `#/management/spaces/list`; }; - editingExistingSpace = () => !!this.props.space; + private editingExistingSpace = () => !!this.props.spaceId; } - -ManageSpacePage.propTypes = { - space: PropTypes.string, - spacesManager: PropTypes.object, - spacesNavState: PropTypes.object.isRequired, - userProfile: PropTypes.object.isRequired, -}; diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/reserved_space_badge.test.js b/x-pack/plugins/spaces/public/views/management/edit_space/reserved_space_badge.test.tsx similarity index 81% rename from x-pack/plugins/spaces/public/views/management/edit_space/reserved_space_badge.test.js rename to x-pack/plugins/spaces/public/views/management/edit_space/reserved_space_badge.test.tsx index 755e6e4bf011e..ae584ba715b63 100644 --- a/x-pack/plugins/spaces/public/views/management/edit_space/reserved_space_badge.test.js +++ b/x-pack/plugins/spaces/public/views/management/edit_space/reserved_space_badge.test.tsx @@ -4,20 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiIcon } from '@elastic/eui'; +import { shallow } from 'enzyme'; import React from 'react'; -import { - EuiIcon -} from '@elastic/eui'; import { ReservedSpaceBadge } from './reserved_space_badge'; -import { - shallow -} from 'enzyme'; const reservedSpace = { - _reserved: true + id: '', + name: '', + _reserved: true, }; -const unreservedSpace = {}; +const unreservedSpace = { + id: '', + name: '', +}; test('it renders without crashing', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/reserved_space_badge.js b/x-pack/plugins/spaces/public/views/management/edit_space/reserved_space_badge.tsx similarity index 58% rename from x-pack/plugins/spaces/public/views/management/edit_space/reserved_space_badge.js rename to x-pack/plugins/spaces/public/views/management/edit_space/reserved_space_badge.tsx index 2f0f96377fcb4..5c358cb4fd5ca 100644 --- a/x-pack/plugins/spaces/public/views/management/edit_space/reserved_space_badge.js +++ b/x-pack/plugins/spaces/public/views/management/edit_space/reserved_space_badge.tsx @@ -5,30 +5,24 @@ */ import React from 'react'; -import PropTypes from 'prop-types'; +import { EuiIcon, EuiToolTip } from '@elastic/eui'; import { isReservedSpace } from '../../../../common'; -import { - EuiIcon, - EuiToolTip, -} from '@elastic/eui'; +import { Space } from '../../../../common/model/space'; +interface Props { + space?: Space; +} -export const ReservedSpaceBadge = (props) => { - const { - space - } = props; +export const ReservedSpaceBadge = (props: Props) => { + const { space } = props; - if (isReservedSpace(space)) { + if (space && isReservedSpace(space)) { return ( - + ); } return null; }; - -ReservedSpaceBadge.propTypes = { - space: PropTypes.object.isRequired -}; diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.test.js b/x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.test.tsx similarity index 87% rename from x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.test.js rename to x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.test.tsx index 7970b2633ca85..f4f9b11f94b2d 100644 --- a/x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.test.js +++ b/x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.test.tsx @@ -4,17 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { shallow } from 'enzyme'; -import { SpaceIdentifier } from './space_identifier'; +import React from 'react'; import { SpaceValidator } from '../lib'; +import { SpaceIdentifier } from './space_identifier'; test('renders without crashing', () => { const props = { - space: {}, + space: { + id: '', + name: '', + }, editable: true, onChange: jest.fn(), - validator: new SpaceValidator() + validator: new SpaceValidator(), }; const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); diff --git a/x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.js b/x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.tsx similarity index 66% rename from x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.js rename to x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.tsx index 4af9003678db1..1f9b006ef91ea 100644 --- a/x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.js +++ b/x-pack/plugins/spaces/public/views/management/edit_space/space_identifier.tsx @@ -4,22 +4,38 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; import { + EuiFieldText, EuiFormRow, EuiLink, - EuiFieldText, } from '@elastic/eui'; +import React, { ChangeEvent, Component, Fragment } from 'react'; +import { Space } from '../../../../common/model/space'; +import { SpaceValidator } from '../lib'; -export class SpaceIdentifier extends Component { - textFieldRef = null; +interface Props { + space: Partial; + editable: boolean; + validator: SpaceValidator; + onChange: (e: ChangeEvent) => void; +} - state = { - editing: false - }; +interface State { + editing: boolean; +} - render() { +export class SpaceIdentifier extends Component { + + private textFieldRef: HTMLInputElement | null = null; + + constructor(props: Props) { + super(props); + this.state = { + editing: false, + }; + } + + public render() { const { id = '' } = this.props.space; @@ -35,7 +51,7 @@ export class SpaceIdentifier extends Component { readOnly={!this.state.editing} placeholder={ this.state.editing || !this.props.editable - ? null + ? undefined : 'The URL identifier is generated from the space name.' } value={id} @@ -47,7 +63,7 @@ export class SpaceIdentifier extends Component { ); } - getLabel = () => { + public getLabel = () => { if (!this.props.editable) { return (

URL identifier

); } @@ -56,11 +72,11 @@ export class SpaceIdentifier extends Component { return (

URL identifier {editLinkText}

); }; - getHelpText = () => { + public getHelpText = () => { return (

If the identifier is engineering, the Kibana URL is
https://my-kibana.example/s/engineering/app/kibana.

); }; - onEditClick = () => { + public onEditClick = () => { this.setState({ editing: !this.state.editing }, () => { @@ -70,15 +86,8 @@ export class SpaceIdentifier extends Component { }); }; - onChange = (e) => { - if (!this.state.editing) return; + public onChange = (e: ChangeEvent) => { + if (!this.state.editing) { return; } this.props.onChange(e); }; } - -SpaceIdentifier.propTypes = { - space: PropTypes.object.isRequired, - editable: PropTypes.bool.isRequired, - onChange: PropTypes.func, - validator: PropTypes.object.isRequired, -}; diff --git a/x-pack/plugins/spaces/public/views/management/lib/index.js b/x-pack/plugins/spaces/public/views/management/lib/index.js deleted file mode 100644 index ab5331a58a742..0000000000000 --- a/x-pack/plugins/spaces/public/views/management/lib/index.js +++ /dev/null @@ -1,14 +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. - */ - -export { - toSpaceIdentifier, - isValidSpaceIdentifier, -} from './space_identifier_utils'; - -export { - SpaceValidator -} from './validate_space'; diff --git a/x-pack/plugins/spaces/server/lib/validate_config.js b/x-pack/plugins/spaces/public/views/management/lib/index.ts similarity index 64% rename from x-pack/plugins/spaces/server/lib/validate_config.js rename to x-pack/plugins/spaces/public/views/management/lib/index.ts index 44493d21fb6d4..4a158168febd8 100644 --- a/x-pack/plugins/spaces/server/lib/validate_config.js +++ b/x-pack/plugins/spaces/public/views/management/lib/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ +export { toSpaceIdentifier, isValidSpaceIdentifier } from './space_identifier_utils'; -export function validateConfig() { - // TODO(legrego): implement if needed. -} +export { SpaceValidator } from './validate_space'; diff --git a/x-pack/plugins/spaces/public/views/management/lib/space_identifier_utils.test.js b/x-pack/plugins/spaces/public/views/management/lib/space_identifier_utils.test.ts similarity index 93% rename from x-pack/plugins/spaces/public/views/management/lib/space_identifier_utils.test.js rename to x-pack/plugins/spaces/public/views/management/lib/space_identifier_utils.test.ts index 1a3cd091bb861..c180f380d6845 100644 --- a/x-pack/plugins/spaces/public/views/management/lib/space_identifier_utils.test.js +++ b/x-pack/plugins/spaces/public/views/management/lib/space_identifier_utils.test.ts @@ -16,7 +16,7 @@ test('it converts everything to lowercase', () => { }); test('it converts non-alphanumeric characters except for "_" to dashes', () => { - const input = `~!@#$%^&*()+-=[]{}\|';:"/.,<>?` + "`"; + const input = `~!@#$%^&*()+-=[]{}\|';:"/.,<>?` + '`'; const expectedResult = new Array(input.length + 1).join('-'); diff --git a/x-pack/plugins/spaces/public/views/management/lib/space_identifier_utils.js b/x-pack/plugins/spaces/public/views/management/lib/space_identifier_utils.ts similarity index 100% rename from x-pack/plugins/spaces/public/views/management/lib/space_identifier_utils.js rename to x-pack/plugins/spaces/public/views/management/lib/space_identifier_utils.ts diff --git a/x-pack/plugins/spaces/public/views/management/lib/validate_space.test.js b/x-pack/plugins/spaces/public/views/management/lib/validate_space.test.ts similarity index 57% rename from x-pack/plugins/spaces/public/views/management/lib/validate_space.test.js rename to x-pack/plugins/spaces/public/views/management/lib/validate_space.test.ts index dac3e417a371c..bcaab2bfa0ec4 100644 --- a/x-pack/plugins/spaces/public/views/management/lib/validate_space.test.js +++ b/x-pack/plugins/spaces/public/views/management/lib/validate_space.test.ts @@ -6,7 +6,7 @@ import { SpaceValidator } from './validate_space'; -let validator; +let validator: SpaceValidator; describe('validateSpaceName', () => { beforeEach(() => { @@ -15,7 +15,8 @@ describe('validateSpaceName', () => { test('it allows a name with special characters', () => { const space = { - name: 'This is the name of my Space! @#$%^&*()_+-=' + id: '', + name: 'This is the name of my Space! @#$%^&*()_+-=', }; expect(validator.validateSpaceName(space)).toEqual({ isInvalid: false }); @@ -23,24 +24,34 @@ describe('validateSpaceName', () => { test('it requires a non-empty value', () => { const space = { - name: '' + id: '', + name: '', }; - expect(validator.validateSpaceName(space)).toEqual({ isInvalid: true, error: `Name is required` }); + expect(validator.validateSpaceName(space)).toEqual({ + isInvalid: true, + error: `Name is required`, + }); }); test('it cannot exceed 1024 characters', () => { const space = { - name: new Array(1026).join('A') + id: '', + name: new Array(1026).join('A'), }; - expect(validator.validateSpaceName(space)).toEqual({ isInvalid: true, error: `Name must not exceed 1024 characters` }); + expect(validator.validateSpaceName(space)).toEqual({ + isInvalid: true, + error: `Name must not exceed 1024 characters`, + }); }); }); describe('validateSpaceDescription', () => { test('is optional', () => { const space = { + id: '', + name: '', }; expect(validator.validateSpaceDescription(space)).toEqual({ isInvalid: false }); @@ -48,17 +59,24 @@ describe('validateSpaceDescription', () => { test('it cannot exceed 2000 characters', () => { const space = { - description: new Array(2002).join('A') + id: '', + name: '', + description: new Array(2002).join('A'), }; - expect(validator.validateSpaceDescription(space)).toEqual({ isInvalid: true, error: `Description must not exceed 2000 characters` }); + expect(validator.validateSpaceDescription(space)).toEqual({ + isInvalid: true, + error: `Description must not exceed 2000 characters`, + }); }); }); describe('validateURLIdentifier', () => { test('it does not validate reserved spaces', () => { const space = { - _reserved: true + id: '', + name: '', + _reserved: true, }; expect(validator.validateURLIdentifier(space)).toEqual({ isInvalid: false }); @@ -66,24 +84,32 @@ describe('validateURLIdentifier', () => { test('it requires a non-empty value', () => { const space = { - id: '' + id: '', + name: '', }; - expect(validator.validateURLIdentifier(space)).toEqual({ isInvalid: true, error: `URL identifier is required` }); + expect(validator.validateURLIdentifier(space)).toEqual({ + isInvalid: true, + error: `URL identifier is required`, + }); }); test('it requires a valid Space Identifier', () => { const space = { - id: 'invalid identifier' + id: 'invalid identifier', + name: '', }; - expect(validator.validateURLIdentifier(space)) - .toEqual({ isInvalid: true, error: 'URL identifier can only contain a-z, 0-9, and the characters "_" and "-"' }); + expect(validator.validateURLIdentifier(space)).toEqual({ + isInvalid: true, + error: 'URL identifier can only contain a-z, 0-9, and the characters "_" and "-"', + }); }); test('it allows a valid Space Identifier', () => { const space = { - id: '01-valid-context-01' + id: '01-valid-context-01', + name: '', }; expect(validator.validateURLIdentifier(space)).toEqual({ isInvalid: false }); diff --git a/x-pack/plugins/spaces/public/views/management/lib/validate_space.js b/x-pack/plugins/spaces/public/views/management/lib/validate_space.ts similarity index 62% rename from x-pack/plugins/spaces/public/views/management/lib/validate_space.js rename to x-pack/plugins/spaces/public/views/management/lib/validate_space.ts index 1aa98aac8e579..66b931e7f4c9a 100644 --- a/x-pack/plugins/spaces/public/views/management/lib/validate_space.js +++ b/x-pack/plugins/spaces/public/views/management/lib/validate_space.ts @@ -3,24 +3,33 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { isValidSpaceIdentifier } from './space_identifier_utils'; import { isReservedSpace } from '../../../../common/is_reserved_space'; +import { Space } from '../../../../common/model/space'; +import { isValidSpaceIdentifier } from './space_identifier_utils'; + +interface SpaceValidatorOptions { + shouldValidate?: boolean; +} export class SpaceValidator { - constructor(options = {}) { - this._shouldValidate = options.shouldValidate; + private shouldValidate: boolean; + + constructor(options: SpaceValidatorOptions = {}) { + this.shouldValidate = options.shouldValidate || false; } - enableValidation() { - this._shouldValidate = true; + public enableValidation() { + this.shouldValidate = true; } - disableValidation() { - this._shouldValidate = false; + public disableValidation() { + this.shouldValidate = false; } - validateSpaceName(space) { - if (!this._shouldValidate) return valid(); + public validateSpaceName(space: Partial) { + if (!this.shouldValidate) { + return valid(); + } if (!space.name) { return invalid(`Name is required`); @@ -33,8 +42,10 @@ export class SpaceValidator { return valid(); } - validateSpaceDescription(space) { - if (!this._shouldValidate) return valid(); + public validateSpaceDescription(space: Partial) { + if (!this.shouldValidate) { + return valid(); + } if (space.description && space.description.length > 2000) { return invalid(`Description must not exceed 2000 characters`); @@ -43,10 +54,14 @@ export class SpaceValidator { return valid(); } - validateURLIdentifier(space) { - if (!this._shouldValidate) return valid(); + public validateURLIdentifier(space: Partial) { + if (!this.shouldValidate) { + return valid(); + } - if (isReservedSpace(space)) return valid(); + if (isReservedSpace(space)) { + return valid(); + } if (!space.id) { return invalid(`URL identifier is required`); @@ -59,7 +74,7 @@ export class SpaceValidator { return valid(); } - validateForSave(space) { + public validateForSave(space: Space) { const { isInvalid: isNameInvalid } = this.validateSpaceName(space); const { isInvalid: isDescriptionInvalid } = this.validateSpaceDescription(space); const { isInvalid: isIdentifierInvalid } = this.validateURLIdentifier(space); @@ -72,15 +87,15 @@ export class SpaceValidator { } } -function invalid(error) { +function invalid(error: string = '') { return { isInvalid: true, - error + error, }; } function valid() { return { - isInvalid: false + isInvalid: false, }; } diff --git a/x-pack/plugins/spaces/public/views/management/page_routes.js b/x-pack/plugins/spaces/public/views/management/page_routes.tsx similarity index 55% rename from x-pack/plugins/spaces/public/views/management/page_routes.js rename to x-pack/plugins/spaces/public/views/management/page_routes.tsx index 17bb29ac6282a..fa7f26a32aa83 100644 --- a/x-pack/plugins/spaces/public/views/management/page_routes.js +++ b/x-pack/plugins/spaces/public/views/management/page_routes.tsx @@ -4,24 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import 'ui/autoload/styles'; import 'plugins/spaces/views/management/manage_spaces.less'; +// @ts-ignore import template from 'plugins/spaces/views/management/template.html'; +// @ts-ignore import { UserProfileProvider } from 'plugins/xpack_main/services/user_profile'; +import 'ui/autoload/styles'; +import { SpacesNavState } from 'plugins/spaces/views/nav_control'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { SpacesGridPage } from './spaces_grid'; -import { ManageSpacePage } from './edit_space'; -import { SpacesManager } from '../../lib/spaces_manager'; - +// @ts-ignore import routes from 'ui/routes'; +import { SpacesManager } from '../../lib/spaces_manager'; +import { ManageSpacePage } from './edit_space'; +import { SpacesGridPage } from './spaces_grid'; const reactRootNodeId = 'manageSpacesReactRoot'; routes.when('/management/spaces/list', { template, - controller: function ($scope, $http, chrome, Private, spacesNavState, spaceSelectorURL) { + controller( + $scope: any, + $http: any, + chrome: any, + Private: any, + spacesNavState: SpacesNavState, + spaceSelectorURL: string + ) { const userProfile = Private(UserProfileProvider); $scope.$$postDigest(() => { @@ -29,23 +39,35 @@ routes.when('/management/spaces/list', { const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL); - render(, domNode); + render( + , + domNode + ); // unmount react on controller destroy $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); + if (domNode) { + unmountComponentAtNode(domNode); + } }); }); - } + }, }); routes.when('/management/spaces/create', { template, - controller: function ($scope, $http, chrome, Private, spacesNavState, spaceSelectorURL) { + controller( + $scope: any, + $http: any, + chrome: any, + Private: any, + spacesNavState: SpacesNavState, + spaceSelectorURL: string + ) { const userProfile = Private(UserProfileProvider); $scope.$$postDigest(() => { @@ -53,50 +75,65 @@ routes.when('/management/spaces/create', { const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL); - render(, domNode); + render( + , + domNode + ); // unmount react on controller destroy $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); + if (domNode) { + unmountComponentAtNode(domNode); + } }); }); - } + }, }); routes.when('/management/spaces/edit', { - redirectTo: '/management/spaces/list' + redirectTo: '/management/spaces/list', }); -routes.when('/management/spaces/edit/:space', { +routes.when('/management/spaces/edit/:spaceId', { template, - controller: function ($scope, $http, $route, chrome, Private, spacesNavState, spaceSelectorURL) { + controller( + $scope: any, + $http: any, + $route: any, + chrome: any, + Private: any, + spacesNavState: SpacesNavState, + spaceSelectorURL: string + ) { const userProfile = Private(UserProfileProvider); $scope.$$postDigest(() => { - const domNode = document.getElementById(reactRootNodeId); - const { space } = $route.current.params; + const { spaceId } = $route.current.params; const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL); - render(, domNode); + render( + , + domNode + ); // unmount react on controller destroy $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); + if (domNode) { + unmountComponentAtNode(domNode); + } }); }); - } + }, }); diff --git a/x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx b/x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx index f627159718452..4a85fc2eaa5f4 100644 --- a/x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx +++ b/x-pack/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx @@ -19,9 +19,10 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; - +// @ts-ignore import { toastNotifications } from 'ui/notify'; +import { SpacesNavState } from 'plugins/spaces/views/nav_control'; import { UserProfile } from '../../../../../xpack_main/public/services/user_profile'; import { isReservedSpace } from '../../../../common'; import { Space } from '../../../../common/model/space'; @@ -32,7 +33,7 @@ import { UnauthorizedPrompt } from '../components/unauthorized_prompt'; interface Props { spacesManager: SpacesManager; - spacesNavState: any; + spacesNavState: SpacesNavState; userProfile: UserProfile; } diff --git a/x-pack/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.js.snap b/x-pack/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.tsx.snap similarity index 97% rename from x-pack/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.js.snap rename to x-pack/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.tsx.snap index 3da9589c10e19..622f863331e79 100644 --- a/x-pack/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.js.snap +++ b/x-pack/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.tsx.snap @@ -20,6 +20,7 @@ exports[`NavControlPopover renders without crashing 1`] = ` size="s" space={ Object { + "id": "", "name": "foo", } } diff --git a/x-pack/plugins/spaces/public/views/nav_control/index.ts b/x-pack/plugins/spaces/public/views/nav_control/index.ts index 475360ac74a3b..541c79a8fd4a3 100644 --- a/x-pack/plugins/spaces/public/views/nav_control/index.ts +++ b/x-pack/plugins/spaces/public/views/nav_control/index.ts @@ -5,3 +5,5 @@ */ import './nav_control'; + +export { SpacesNavState } from './nav_control'; diff --git a/x-pack/plugins/spaces/public/views/nav_control/nav_control.js b/x-pack/plugins/spaces/public/views/nav_control/nav_control.js deleted file mode 100644 index 5ed4b57e87ebe..0000000000000 --- a/x-pack/plugins/spaces/public/views/nav_control/nav_control.js +++ /dev/null @@ -1,65 +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 { constant } from 'lodash'; -import { chromeNavControlsRegistry } from 'ui/registry/chrome_nav_controls'; -import { uiModules } from 'ui/modules'; -import { SpacesManager } from 'plugins/spaces/lib/spaces_manager'; -import template from 'plugins/spaces/views/nav_control/nav_control.html'; -import 'plugins/spaces/views/nav_control/nav_control.less'; -import { UserProfileProvider } from 'plugins/xpack_main/services/user_profile'; - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { NavControlPopover } from 'plugins/spaces/views/nav_control/nav_control_popover'; - -chromeNavControlsRegistry.register(constant({ - name: 'spaces', - order: 90, - template -})); - -const module = uiModules.get('spaces_nav', ['kibana']); - -let spacesManager; - -module.controller('spacesNavController', ($scope, $http, chrome, Private, activeSpace) => { - const userProfile = Private(UserProfileProvider); - - const domNode = document.getElementById(`spacesNavReactRoot`); - const spaceSelectorURL = chrome.getInjected('spaceSelectorURL'); - - spacesManager = new SpacesManager($http, chrome, spaceSelectorURL); - - let mounted = false; - - $scope.$parent.$watch('isVisible', function (isVisible) { - if (isVisible && !mounted) { - render(, domNode); - mounted = true; - } - }); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); - mounted = false; - }); - -}); - -module.service('spacesNavState', (activeSpace) => { - return { - getActiveSpace: () => { - return activeSpace.space; - }, - refreshSpacesList: () => { - if (spacesManager) { - spacesManager.requestRefresh(); - } - } - }; -}); diff --git a/x-pack/plugins/spaces/public/views/nav_control/nav_control.tsx b/x-pack/plugins/spaces/public/views/nav_control/nav_control.tsx new file mode 100644 index 0000000000000..690ae64f77536 --- /dev/null +++ b/x-pack/plugins/spaces/public/views/nav_control/nav_control.tsx @@ -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 { constant } from 'lodash'; +import { SpacesManager } from 'plugins/spaces/lib/spaces_manager'; +// @ts-ignore +import template from 'plugins/spaces/views/nav_control/nav_control.html'; +import 'plugins/spaces/views/nav_control/nav_control.less'; +import { UserProfileProvider } from 'plugins/xpack_main/services/user_profile'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +// @ts-ignore +import { chromeNavControlsRegistry } from 'ui/registry/chrome_nav_controls'; + +import { NavControlPopover } from 'plugins/spaces/views/nav_control/nav_control_popover'; +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { Space } from '../../../common/model/space'; + +chromeNavControlsRegistry.register( + constant({ + name: 'spaces', + order: 90, + template, + }) +); + +const module = uiModules.get('spaces_nav', ['kibana']); + +export interface SpacesNavState { + getActiveSpace: () => Space; + refreshSpacesList: () => void; +} + +let spacesManager: SpacesManager; + +module.controller( + 'spacesNavController', + ($scope: any, $http: any, chrome: any, Private: any, activeSpace: any) => { + const userProfile = Private(UserProfileProvider); + + const domNode = document.getElementById(`spacesNavReactRoot`); + const spaceSelectorURL = chrome.getInjected('spaceSelectorURL'); + + spacesManager = new SpacesManager($http, chrome, spaceSelectorURL); + + let mounted = false; + + $scope.$parent.$watch('isVisible', function isVisibleWatcher(isVisible: boolean) { + if (isVisible && !mounted) { + render( + , + domNode + ); + mounted = true; + } + }); + + // unmount react on controller destroy + $scope.$on('$destroy', () => { + if (domNode) { + unmountComponentAtNode(domNode); + } + mounted = false; + }); + } +); + +module.service('spacesNavState', (activeSpace: any) => { + return { + getActiveSpace: () => { + return activeSpace.space; + }, + refreshSpacesList: () => { + if (spacesManager) { + spacesManager.requestRefresh(); + } + }, + } as SpacesNavState; +}); diff --git a/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.test.js b/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx similarity index 59% rename from x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.test.js rename to x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx index 9252055e33957..67f6e81df956e 100644 --- a/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.test.js +++ b/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx @@ -4,30 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ +import { mount, shallow } from 'enzyme'; import React from 'react'; -import { shallow, mount } from 'enzyme'; -import { NavControlPopover } from './nav_control_popover'; -import { SpacesManager } from '../../lib/spaces_manager'; import { SpaceAvatar } from '../../components'; +import { SpacesManager } from '../../lib/spaces_manager'; +import { NavControlPopover } from './nav_control_popover'; const mockChrome = { - addBasePath: jest.fn((a) => a) + addBasePath: jest.fn((a: string) => a), }; const createMockHttpAgent = (withSpaces = false) => { + const spaces = [ + { + id: '', + name: 'space 1', + }, + { + id: '', + name: 'space 2', + }, + ]; const mockHttpAgent = { get: async () => { - const result = withSpaces ? [{ - name: 'space 1' - }, { - name: 'space 2' - }] : []; + const result = withSpaces ? spaces : []; return { - data: result + data: result, }; - } + }, }; return mockHttpAgent; }; @@ -35,37 +41,41 @@ const createMockHttpAgent = (withSpaces = false) => { describe('NavControlPopover', () => { it('renders without crashing', () => { const activeSpace = { - space: { name: 'foo' }, - valid: true + space: { id: '', name: 'foo' }, + valid: true, }; - const spacesManager = new SpacesManager(createMockHttpAgent(), mockChrome); + const spacesManager = new SpacesManager(createMockHttpAgent(), mockChrome, '/'); - const wrapper = shallow( true }} - />); + const wrapper = shallow( + true }} + /> + ); expect(wrapper).toMatchSnapshot(); }); it('renders a SpaceAvatar with the active space', async () => { const activeSpace = { - space: { name: 'foo' }, - valid: true + space: { id: '', name: 'foo' }, + valid: true, }; const mockAgent = createMockHttpAgent(true); - const spacesManager = new SpacesManager(mockAgent, mockChrome); + const spacesManager = new SpacesManager(mockAgent, mockChrome, '/'); - const wrapper = mount( true }} - />); + const wrapper = mount( + true }} + /> + ); - return new Promise((resolve) => { + return new Promise(resolve => { setTimeout(() => { expect(wrapper.state().spaces).toHaveLength(2); wrapper.update(); diff --git a/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.tsx b/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.tsx index bdf45b7c61bac..38a241ef1738c 100644 --- a/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.tsx +++ b/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.tsx @@ -17,7 +17,7 @@ interface Props { spacesManager: SpacesManager; activeSpace: { valid: boolean; - error: string; + error?: string; space: Space; }; userProfile: UserProfile; diff --git a/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.js.snap b/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.js.snap deleted file mode 100644 index 9f747481e626e..0000000000000 --- a/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.js.snap +++ /dev/null @@ -1,107 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`it renders without crashing 1`] = ` -
-
-
-
-
- - - - - - -
-
-

- Select your space -

-
-
-
-
-
-
-

- You can change your space at anytime. -

-
-
-
-
-
-
-
-
-
-
-
- No spaces match search criteria -
-
-
-
-
-
-`; diff --git a/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.tsx.snap b/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.tsx.snap new file mode 100644 index 0000000000000..47be61bc3c148 --- /dev/null +++ b/x-pack/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.tsx.snap @@ -0,0 +1,88 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`it renders without crashing 1`] = ` + + + + +
+ +
+ + +

+ Select your space +

+
+
+
+ + + + +

+ You can change your space at anytime. +

+
+
+
+ + + + + + No spaces match search criteria + + +
+
+
+`; diff --git a/x-pack/plugins/spaces/public/views/space_selector/index.js b/x-pack/plugins/spaces/public/views/space_selector/index.tsx similarity index 53% rename from x-pack/plugins/spaces/public/views/space_selector/index.js rename to x-pack/plugins/spaces/public/views/space_selector/index.tsx index 210e8ac8d5f82..e9b9a1795cf20 100644 --- a/x-pack/plugins/spaces/public/views/space_selector/index.js +++ b/x-pack/plugins/spaces/public/views/space_selector/index.tsx @@ -4,31 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SpacesManager } from 'plugins/spaces/lib/spaces_manager'; +// @ts-ignore +import template from 'plugins/spaces/views/space_selector/space_selector.html'; +import 'plugins/spaces/views/space_selector/space_selector.less'; import 'ui/autoload/styles'; import chrome from 'ui/chrome'; -import 'plugins/spaces/views/space_selector/space_selector.less'; -import template from 'plugins/spaces/views/space_selector/space_selector.html'; -import { SpacesManager } from 'plugins/spaces/lib/spaces_manager'; +// @ts-ignore import { uiModules } from 'ui/modules'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { Space } from '../../../common/model/space'; import { SpaceSelector } from './space_selector'; const module = uiModules.get('spaces_selector', []); -module.controller('spacesSelectorController', ($scope, $http, spaces, spaceSelectorURL) => { - const domNode = document.getElementById('spaceSelectorRoot'); +module.controller( + 'spacesSelectorController', + ($scope: any, $http: any, spaces: Space[], spaceSelectorURL: string) => { + const domNode = document.getElementById('spaceSelectorRoot'); - const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL); + const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL); - render(, domNode); + render(, domNode); - // unmount react on controller destroy - $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); - }); -}); + // unmount react on controller destroy + $scope.$on('$destroy', () => { + if (domNode) { + unmountComponentAtNode(domNode); + } + }); + } +); -chrome - .setVisible(false) - .setRootTemplate(template); +chrome.setVisible(false).setRootTemplate(template); diff --git a/x-pack/plugins/spaces/public/views/space_selector/space_selector.test.js b/x-pack/plugins/spaces/public/views/space_selector/space_selector.test.tsx similarity index 52% rename from x-pack/plugins/spaces/public/views/space_selector/space_selector.test.js rename to x-pack/plugins/spaces/public/views/space_selector/space_selector.test.tsx index eccdddb16c1e7..b748d8aaf81e2 100644 --- a/x-pack/plugins/spaces/public/views/space_selector/space_selector.test.js +++ b/x-pack/plugins/spaces/public/views/space_selector/space_selector.test.tsx @@ -4,23 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ +import { render, shallow } from 'enzyme'; import React from 'react'; -import { SpaceSelector } from './space_selector'; import chrome from 'ui/chrome'; -import renderer from 'react-test-renderer'; -import { render, shallow } from 'enzyme'; +import { Space } from '../../../common/model/space'; import { SpacesManager } from '../../lib/spaces_manager'; +import { SpaceSelector } from './space_selector'; - -function getHttpAgent(spaces = []) { - const httpAgent = () => { }; +function getHttpAgent(spaces: Space[] = []) { + const httpAgent: any = () => { + return; + }; httpAgent.get = jest.fn(() => Promise.resolve({ data: spaces })); return httpAgent; } -function getSpacesManager(spaces = []) { - const manager = new SpacesManager(getHttpAgent(spaces), chrome); +function getSpacesManager(spaces: Space[] = []) { + const manager = new SpacesManager(getHttpAgent(spaces), chrome, '/'); const origGet = manager.getSpaces; manager.getSpaces = jest.fn(origGet); @@ -28,52 +29,45 @@ function getSpacesManager(spaces = []) { return manager; } - test('it renders without crashing', () => { const spacesManager = getSpacesManager(); - const component = renderer.create( - - ); + const component = shallow(); expect(component).toMatchSnapshot(); }); test('it uses the spaces on props, when provided', () => { const spacesManager = getSpacesManager(); - const spaces = [{ - id: 'space-1', - name: 'Space 1', - description: 'This is the first space', - }]; + const spaces = [ + { + id: 'space-1', + name: 'Space 1', + description: 'This is the first space', + }, + ]; - const component = render( - - ); + const component = render(); - return Promise - .resolve() - .then(() => { - expect(component.find('.spaceCard')).toHaveLength(1); - expect(spacesManager.getSpaces).toHaveBeenCalledTimes(0); - }); + return Promise.resolve().then(() => { + expect(component.find('.spaceCard')).toHaveLength(1); + expect(spacesManager.getSpaces).toHaveBeenCalledTimes(0); + }); }); test('it queries for spaces when not provided on props', () => { - const spaces = [{ - id: 'space-1', - name: 'Space 1', - description: 'This is the first space', - }]; + const spaces = [ + { + id: 'space-1', + name: 'Space 1', + description: 'This is the first space', + }, + ]; const spacesManager = getSpacesManager(spaces); - shallow( - - ); + shallow(); - return Promise - .resolve() - .then(() => { - expect(spacesManager.getSpaces).toHaveBeenCalledTimes(1); - }); + return Promise.resolve().then(() => { + expect(spacesManager.getSpaces).toHaveBeenCalledTimes(1); + }); }); diff --git a/x-pack/plugins/spaces/public/views/space_selector/space_selector.js b/x-pack/plugins/spaces/public/views/space_selector/space_selector.tsx similarity index 58% rename from x-pack/plugins/spaces/public/views/space_selector/space_selector.js rename to x-pack/plugins/spaces/public/views/space_selector/space_selector.tsx index 3508bdefe3b0b..fe596d761f999 100644 --- a/x-pack/plugins/spaces/public/views/space_selector/space_selector.js +++ b/x-pack/plugins/spaces/public/views/space_selector/space_selector.tsx @@ -4,68 +4,82 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; import { + EuiFieldSearch, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, EuiPage, - EuiPageHeader, EuiPageBody, EuiPageContent, + EuiPageHeader, EuiPageHeaderSection, - EuiIcon, EuiSpacer, EuiText, EuiTitle, - EuiFieldSearch, - EuiFlexGroup, - EuiFlexItem, } from '@elastic/eui'; -import { SpaceCards } from '../components/space_cards'; +import { SpacesManager } from 'plugins/spaces/lib'; +import React, { Component, Fragment } from 'react'; import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../common/constants'; +import { Space } from '../../../common/model/space'; +import { SpaceCards } from '../components/space_cards'; -export class SpaceSelector extends Component { - state = { - loading: false, - searchTerm: '', - spaces: [] - }; +interface Props { + spaces?: Space[]; + spacesManager: SpacesManager; +} - constructor(props) { +interface State { + loading: boolean; + searchTerm: string; + spaces: Space[]; +} + +export class SpaceSelector extends Component { + constructor(props: Props) { super(props); + + const state: State = { + loading: false, + searchTerm: '', + spaces: [], + }; + if (Array.isArray(props.spaces)) { - this.state.spaces = [...props.spaces]; + state.spaces = [...props.spaces]; } + + this.state = state; } - componentDidMount() { + public componentDidMount() { if (this.state.spaces.length === 0) { this.loadSpaces(); } } - loadSpaces() { + public loadSpaces() { this.setState({ loading: true }); const { spacesManager } = this.props; - spacesManager.getSpaces() - .then(spaces => { - this.setState({ - loading: false, - spaces - }); + spacesManager.getSpaces().then(spaces => { + this.setState({ + loading: false, + spaces, }); + }); } - render() { - const { - spaces, - searchTerm, - } = this.state; + public render() { + const { spaces, searchTerm } = this.state; let filteredSpaces = spaces; if (searchTerm) { - filteredSpaces = spaces - .filter(space => space.name.toLowerCase().indexOf(searchTerm) >= 0 || space.description.toLowerCase().indexOf(searchTerm) >= 0); + filteredSpaces = spaces.filter( + space => + space.name.toLowerCase().indexOf(searchTerm) >= 0 || + (space.description || '').toLowerCase().indexOf(searchTerm) >= 0 + ); } return ( @@ -80,17 +94,23 @@ export class SpaceSelector extends Component { -

Select your space

+

Select your space

- - + {this.getSearchField()} -

You can change your space at anytime.

+ +

You can change your space at anytime.

+
@@ -98,21 +118,25 @@ export class SpaceSelector extends Component { - { - filteredSpaces.length === 0 && + {filteredSpaces.length === 0 && ( - No spaces match search criteria + + No spaces match search criteria + - } - + )}
); } - getSearchField = () => { + public getSearchField = () => { if (!this.props.spaces || this.props.spaces.length < SPACE_SEARCH_COUNT_THRESHOLD) { return null; } @@ -122,24 +146,20 @@ export class SpaceSelector extends Component { className="spaceSelector__searchField" placeholder="Find a space" incremental={true} + // @ts-ignore onSearch={this.onSearch} /> ); - } + }; - onSearch = (searchTerm = '') => { + public onSearch = (searchTerm = '') => { this.setState({ - searchTerm: searchTerm.trim().toLowerCase() + searchTerm: searchTerm.trim().toLowerCase(), }); - } + }; - onSelectSpace = (space) => { + public onSelectSpace = (space: Space) => { this.props.spacesManager.changeSelectedSpace(space); - } + }; } - -SpaceSelector.propTypes = { - spaces: PropTypes.array, - spacesManager: PropTypes.object.isRequired -}; diff --git a/x-pack/plugins/spaces/server/lib/__snapshots__/spaces_url_parser.test.js.snap b/x-pack/plugins/spaces/server/lib/__snapshots__/spaces_url_parser.test.ts.snap similarity index 100% rename from x-pack/plugins/spaces/server/lib/__snapshots__/spaces_url_parser.test.js.snap rename to x-pack/plugins/spaces/server/lib/__snapshots__/spaces_url_parser.test.ts.snap diff --git a/x-pack/plugins/spaces/server/lib/check_license.js b/x-pack/plugins/spaces/server/lib/check_license.ts similarity index 73% rename from x-pack/plugins/spaces/server/lib/check_license.js rename to x-pack/plugins/spaces/server/lib/check_license.ts index f88e8f8221f00..e7fc63e724feb 100644 --- a/x-pack/plugins/spaces/server/lib/check_license.js +++ b/x-pack/plugins/spaces/server/lib/check_license.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -/** - * @typedef {Object} LicenseCheckResult Result of the license check. - * @property {boolean} showSpaces Indicates whether spaces should be enabled - */ +export interface LicenseCheckResult { + showSpaces: boolean; +} /** * Returns object that defines behavior of the spaces related features based @@ -15,10 +14,10 @@ * @param {XPackInfo} xPackInfo XPackInfo instance to extract license information from. * @returns {LicenseCheckResult} */ -export function checkLicense(xPackInfo) { +export function checkLicense(xPackInfo: any): LicenseCheckResult { if (!xPackInfo.isAvailable()) { return { - showSpaces: false + showSpaces: false, }; } @@ -26,11 +25,11 @@ export function checkLicense(xPackInfo) { if (!isAnyXpackLicense) { return { - showSpaces: false + showSpaces: false, }; } return { - showSpaces: true + showSpaces: true, }; } diff --git a/x-pack/plugins/spaces/server/lib/create_default_space.test.js b/x-pack/plugins/spaces/server/lib/create_default_space.test.ts similarity index 74% rename from x-pack/plugins/spaces/server/lib/create_default_space.test.js rename to x-pack/plugins/spaces/server/lib/create_default_space.test.ts index a3afd24bb95fc..d0cf2812cc26d 100644 --- a/x-pack/plugins/spaces/server/lib/create_default_space.test.js +++ b/x-pack/plugins/spaces/server/lib/create_default_space.test.ts @@ -3,28 +3,29 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +jest.mock('../../../../server/lib/get_client_shield', () => ({ + getClient: jest.fn(), +})); + import Boom from 'boom'; +// @ts-ignore import { getClient } from '../../../../server/lib/get_client_shield'; import { createDefaultSpace } from './create_default_space'; -jest.mock('../../../../server/lib/get_client_shield', () => ({ - getClient: jest.fn() -})); - let mockCallWithRequest; beforeEach(() => { mockCallWithRequest = jest.fn(); getClient.mockReturnValue({ - callWithRequest: mockCallWithRequest + callWithRequest: mockCallWithRequest, }); }); - -const createMockServer = (settings = {}) => { - - const { - defaultExists = false, - simulateErrorCondition = false - } = settings; +interface MockServerSettings { + defaultExists?: boolean; + simulateErrorCondition?: boolean; + [invalidKeys: string]: any; +} +const createMockServer = (settings: MockServerSettings = {}) => { + const { defaultExists = false, simulateErrorCondition = false } = settings; const mockGet = jest.fn().mockImplementation(() => { if (simulateErrorCondition) { @@ -37,28 +38,28 @@ const createMockServer = (settings = {}) => { throw Boom.notFound('unit test: default space not found'); }); - const mockCreate = jest.fn().mockReturnValue(); + const mockCreate = jest.fn().mockReturnValue(null); const mockServer = { config: jest.fn().mockReturnValue({ - get: jest.fn() + get: jest.fn(), }), savedObjects: { SavedObjectsClient: { errors: { - isNotFoundError: (e) => e.message === 'unit test: default space not found' - } + isNotFoundError: (e: Error) => e.message === 'unit test: default space not found', + }, }, getSavedObjectsRepository: jest.fn().mockImplementation(() => { return { get: mockGet, create: mockCreate, }; - }) - } + }), + }, }; - mockServer.config().get.mockImplementation(key => { + mockServer.config().get.mockImplementation((key: string) => { return settings[key]; }); @@ -67,7 +68,7 @@ const createMockServer = (settings = {}) => { test(`it creates the default space when one does not exist`, async () => { const server = createMockServer({ - defaultExists: false + defaultExists: false, }); await createDefaultSpace(server); @@ -78,14 +79,19 @@ test(`it creates the default space when one does not exist`, async () => { expect(repository.create).toHaveBeenCalledTimes(1); expect(repository.create).toHaveBeenCalledWith( 'space', - { "_reserved": true, "description": "This is your default space!", "name": "Default", "color": "#00bfb3" }, - { "id": "default" } + { + _reserved: true, + description: 'This is your default space!', + name: 'Default', + color: '#00bfb3', + }, + { id: 'default' } ); }); test(`it does not attempt to recreate the default space if it already exists`, async () => { const server = createMockServer({ - defaultExists: true + defaultExists: true, }); await createDefaultSpace(server); diff --git a/x-pack/plugins/spaces/server/lib/create_default_space.js b/x-pack/plugins/spaces/server/lib/create_default_space.ts similarity index 63% rename from x-pack/plugins/spaces/server/lib/create_default_space.js rename to x-pack/plugins/spaces/server/lib/create_default_space.ts index 625b9229f9bd0..1e0e28c2f98b8 100644 --- a/x-pack/plugins/spaces/server/lib/create_default_space.js +++ b/x-pack/plugins/spaces/server/lib/create_default_space.ts @@ -3,36 +3,43 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +// @ts-ignore import { getClient } from '../../../../server/lib/get_client_shield'; import { DEFAULT_SPACE_ID } from '../../common/constants'; -export async function createDefaultSpace(server) { +export async function createDefaultSpace(server: any) { const { callWithInternalUser: callCluster } = getClient(server); const { getSavedObjectsRepository, SavedObjectsClient } = server.savedObjects; const savedObjectsRepository = getSavedObjectsRepository(callCluster); - const defaultSpaceExists = await doesDefaultSpaceExist(SavedObjectsClient, savedObjectsRepository); + const defaultSpaceExists = await doesDefaultSpaceExist( + SavedObjectsClient, + savedObjectsRepository + ); if (defaultSpaceExists) { return; } const options = { - id: DEFAULT_SPACE_ID + id: DEFAULT_SPACE_ID, }; - await savedObjectsRepository.create('space', { - name: 'Default', - description: 'This is your default space!', - color: '#00bfb3', - _reserved: true - }, options); + await savedObjectsRepository.create( + 'space', + { + name: 'Default', + description: 'This is your default space!', + color: '#00bfb3', + _reserved: true, + }, + options + ); } -async function doesDefaultSpaceExist(SavedObjectsClient, savedObjectsRepository) { +async function doesDefaultSpaceExist(SavedObjectsClient: any, savedObjectsRepository: any) { try { await savedObjectsRepository.get('space', DEFAULT_SPACE_ID); return true; diff --git a/x-pack/plugins/spaces/server/lib/create_spaces_service.test.js b/x-pack/plugins/spaces/server/lib/create_spaces_service.test.ts similarity index 73% rename from x-pack/plugins/spaces/server/lib/create_spaces_service.test.js rename to x-pack/plugins/spaces/server/lib/create_spaces_service.test.ts index c27a505616018..3eec453554702 100644 --- a/x-pack/plugins/spaces/server/lib/create_spaces_service.test.js +++ b/x-pack/plugins/spaces/server/lib/create_spaces_service.test.ts @@ -4,28 +4,29 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createSpacesService } from "./create_spaces_service"; import { DEFAULT_SPACE_ID } from '../../common/constants'; +import { createSpacesService } from './create_spaces_service'; -const createRequest = (spaceId, serverBasePath = '') => ({ - getBasePath: () => spaceId && spaceId !== DEFAULT_SPACE_ID ? `${serverBasePath}/s/${spaceId}` : serverBasePath +const createRequest = (spaceId?: string, serverBasePath = '') => ({ + getBasePath: () => + spaceId && spaceId !== DEFAULT_SPACE_ID ? `${serverBasePath}/s/${spaceId}` : serverBasePath, }); -const createMockServer = (config) => { +const createMockServer = (config: any) => { return { config: jest.fn(() => { return { - get: jest.fn((key) => { + get: jest.fn((key: string) => { return config[key]; - }) + }), }; - }) + }), }; }; test('returns the default space ID', () => { const server = createMockServer({ - 'server.basePath': '' + 'server.basePath': '', }); const service = createSpacesService(server); @@ -35,7 +36,7 @@ test('returns the default space ID', () => { test('returns the id for the current space', () => { const request = createRequest('my-space-context'); const server = createMockServer({ - 'server.basePath': '' + 'server.basePath': '', }); const service = createSpacesService(server); @@ -45,7 +46,7 @@ test('returns the id for the current space', () => { test(`returns the id for the current space when a server basepath is defined`, () => { const request = createRequest('my-space-context', '/foo'); const server = createMockServer({ - 'server.basePath': '/foo' + 'server.basePath': '/foo', }); const service = createSpacesService(server); diff --git a/x-pack/plugins/spaces/server/lib/get_active_space.js b/x-pack/plugins/spaces/server/lib/get_active_space.ts similarity index 65% rename from x-pack/plugins/spaces/server/lib/get_active_space.js rename to x-pack/plugins/spaces/server/lib/get_active_space.ts index 143ed1b3e5dda..907b7b164b69b 100644 --- a/x-pack/plugins/spaces/server/lib/get_active_space.js +++ b/x-pack/plugins/spaces/server/lib/get_active_space.ts @@ -4,16 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Space } from '../../common/model/space'; import { wrapError } from './errors'; +import { SpacesClient } from './spaces_client'; import { getSpaceIdFromPath } from './spaces_url_parser'; -export async function getActiveSpace(spacesClient, requestBasePath, serverBasePath) { +export async function getActiveSpace( + spacesClient: SpacesClient, + requestBasePath: string, + serverBasePath: string +): Promise { const spaceId = getSpaceIdFromPath(requestBasePath, serverBasePath); try { return spacesClient.get(spaceId); - } - catch (e) { + } catch (e) { throw wrapError(e); } } diff --git a/x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.test.js b/x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.test.ts similarity index 78% rename from x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.test.js rename to x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.test.ts index 490c1c13ec7ab..ef4cb9b01ec1e 100644 --- a/x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.test.js +++ b/x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.test.ts @@ -4,62 +4,72 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getSpacesUsageCollector } from './get_spaces_usage_collector'; +import { getSpacesUsageCollector, UsageStats } from './get_spaces_usage_collector'; -function getServerMock(customization) { +function getServerMock(customization?: any) { class MockUsageCollector { - constructor(_server, { fetch }) { + private fetch: any; + + constructor(server: any, { fetch }: any) { this.fetch = fetch; } + // to make typescript happy + public fakeFetchUsage() { + return this.fetch; + } } const getLicenseCheckResults = jest.fn().mockReturnValue({}); const defaultServerMock = { plugins: { security: { - isAuthenticated: jest.fn().mockReturnValue(true) + isAuthenticated: jest.fn().mockReturnValue(true), }, xpack_main: { info: { isAvailable: jest.fn().mockReturnValue(true), feature: () => ({ - getLicenseCheckResults + getLicenseCheckResults, }), license: { isOneOf: jest.fn().mockReturnValue(false), getType: jest.fn().mockReturnValue('platinum'), }, - toJSON: () => ({ b: 1 }) - } - } + toJSON: () => ({ b: 1 }), + }, + }, + }, + expose: () => { + return; + }, + log: () => { + return; }, - expose: () => { }, - log: () => { }, config: () => ({ - get: key => { + get: (key: string) => { if (key === 'xpack.spaces.enabled') { return true; } - } + }, }), usage: { collectorSet: { - makeUsageCollector: options => { - return new MockUsageCollector(this, options); - } - } + makeUsageCollector: (options: any) => { + return new MockUsageCollector(defaultServerMock, options); + }, + }, }, savedObjects: { getSavedObjectsRepository: jest.fn(() => { return { find() { return { - saved_objects: ['a', 'b'] + saved_objects: ['a', 'b'], }; - } + }, }; - }) - } + }), + }, }; return Object.assign(defaultServerMock, customization); } @@ -75,15 +85,17 @@ test('sets enabled to false when spaces is turned off', async () => { const serverMock = getServerMock({ config: () => ({ get: mockConfigGet }) }); const callClusterMock = jest.fn(); const { fetch: getSpacesUsage } = getSpacesUsageCollector(serverMock); - const usageStats = await getSpacesUsage(callClusterMock); + const usageStats: UsageStats = await getSpacesUsage(callClusterMock); expect(usageStats.enabled).toBe(false); }); describe('with a basic license', async () => { - let usageStats; + let usageStats: UsageStats; beforeAll(async () => { const serverWithBasicLicenseMock = getServerMock(); - serverWithBasicLicenseMock.plugins.xpack_main.info.license.getType = jest.fn().mockReturnValue('basic'); + serverWithBasicLicenseMock.plugins.xpack_main.info.license.getType = jest + .fn() + .mockReturnValue('basic'); const callClusterMock = jest.fn(() => Promise.resolve({})); const { fetch: getSpacesUsage } = getSpacesUsageCollector(serverWithBasicLicenseMock); usageStats = await getSpacesUsage(callClusterMock); @@ -103,7 +115,7 @@ describe('with a basic license', async () => { }); describe('with no license', async () => { - let usageStats; + let usageStats: UsageStats; beforeAll(async () => { const serverWithNoLicenseMock = getServerMock(); serverWithNoLicenseMock.plugins.xpack_main.info.isAvailable = jest.fn().mockReturnValue(false); @@ -126,10 +138,12 @@ describe('with no license', async () => { }); describe('with platinum license', async () => { - let usageStats; + let usageStats: UsageStats; beforeAll(async () => { const serverWithPlatinumLicenseMock = getServerMock(); - serverWithPlatinumLicenseMock.plugins.xpack_main.info.license.getType = jest.fn().mockReturnValue('platinum'); + serverWithPlatinumLicenseMock.plugins.xpack_main.info.license.getType = jest + .fn() + .mockReturnValue('platinum'); const callClusterMock = jest.fn(() => Promise.resolve({})); const { fetch: getSpacesUsage } = getSpacesUsageCollector(serverWithPlatinumLicenseMock); usageStats = await getSpacesUsage(callClusterMock); diff --git a/x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.js b/x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.ts similarity index 80% rename from x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.js rename to x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.ts index 455d2f0932b32..9361bfaf18b37 100644 --- a/x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.js +++ b/x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.ts @@ -3,9 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { KIBANA_SPACES_STATS_TYPE } from '../../common/constants'; +// @ts-ignore import { KIBANA_STATS_TYPE_MONITORING } from '../../../monitoring/common/constants'; +import { KIBANA_SPACES_STATS_TYPE } from '../../common/constants'; /** * @@ -15,8 +15,10 @@ import { KIBANA_STATS_TYPE_MONITORING } from '../../../monitoring/common/constan * @param withinDayRange * @return {ReportingUsageStats} */ -async function getSpacesUsage(callCluster, server, spacesAvailable) { - if (!spacesAvailable) return {}; +async function getSpacesUsage(callCluster: any, server: any, spacesAvailable: boolean) { + if (!spacesAvailable) { + return {}; + } const { getSavedObjectsRepository } = server.savedObjects; @@ -29,15 +31,20 @@ async function getSpacesUsage(callCluster, server, spacesAvailable) { }; } +export interface UsageStats { + available: boolean; + enabled: boolean; + count?: number; +} /* * @param {Object} server * @return {Object} kibana usage stats type collection object */ -export function getSpacesUsageCollector(server) { +export function getSpacesUsageCollector(server: any) { const { collectorSet } = server.usage; return collectorSet.makeUsageCollector({ type: KIBANA_SPACES_STATS_TYPE, - fetch: async callCluster => { + fetch: async (callCluster: any) => { const xpackInfo = server.plugins.xpack_main.info; const config = server.config(); const available = xpackInfo && xpackInfo.isAvailable(); // some form of spaces is available for all valid licenses @@ -50,7 +57,7 @@ export function getSpacesUsageCollector(server) { available, enabled: spacesAvailableAndEnabled, // similar behavior as _xpack API in ES ...usageStats, - }; + } as UsageStats; }, /* @@ -58,18 +65,17 @@ export function getSpacesUsageCollector(server) { * 1. Make this data part of the "kibana_stats" type * 2. Organize the payload in the usage.xpack.spaces namespace of the data payload */ - formatForBulkUpload: result => { + formatForBulkUpload: (result: UsageStats) => { return { type: KIBANA_STATS_TYPE_MONITORING, payload: { usage: { xpack: { - spaces: result - } - } - } + spaces: result, + }, + }, + }, }; - - } + }, }); } diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/__snapshots__/spaces_saved_objects_client.test.js.snap b/x-pack/plugins/spaces/server/lib/saved_objects_client/__snapshots__/spaces_saved_objects_client.test.ts.snap similarity index 100% rename from x-pack/plugins/spaces/server/lib/saved_objects_client/__snapshots__/spaces_saved_objects_client.test.js.snap rename to x-pack/plugins/spaces/server/lib/saved_objects_client/__snapshots__/spaces_saved_objects_client.test.ts.snap diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_types.ts b/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_types.ts new file mode 100644 index 0000000000000..b2cdc09d66a1b --- /dev/null +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_types.ts @@ -0,0 +1,97 @@ +/* + * 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 interface BaseOptions { + namespace?: string; +} + +export interface CreateOptions extends BaseOptions { + id?: string; + override?: boolean; +} + +export interface BulkCreateObject { + id?: string; + type: string; + attributes: SavedObjectAttributes; + extraDocumentProperties?: string[]; +} + +export interface BulkCreateResponse { + savedObjects: SavedObject[]; +} + +export interface FindOptions extends BaseOptions { + page?: number; + perPage?: number; + sortField?: string; + sortOrder?: string; + fields?: string[]; + type?: string | string[]; +} + +export interface FindResponse { + savedObjects: SavedObject[]; + total: number; + perPage: number; + page: number; +} + +export interface UpdateOptions extends BaseOptions { + version?: number; +} + +export interface BulkGetObject { + id: string; + type: string; +} +export type BulkGetObjects = BulkGetObject[]; + +export interface BulkGetResponse { + savedObjects: SavedObject[]; +} + +export interface SavedObjectAttributes { + [key: string]: string | number | boolean | null; +} + +export interface SavedObject { + id: string; + type: string; + version?: number; + updatedAt?: string; + error?: { + message: string; + }; + attributes: SavedObjectAttributes; +} + +export interface SavedObjectsClient { + errors: any; + create: ( + type: string, + attributes: SavedObjectAttributes, + options?: CreateOptions + ) => Promise; + bulkCreate: (objects: BulkCreateObject[], options?: CreateOptions) => Promise; + delete: (type: string, id: string, options?: BaseOptions) => Promise<{}>; + find: (options: FindOptions) => Promise; + bulkGet: (objects: BulkGetObjects, options?: BaseOptions) => Promise; + get: (type: string, id: string, options?: BaseOptions) => Promise; + update: ( + type: string, + id: string, + attributes: SavedObjectAttributes, + options?: UpdateOptions + ) => Promise; +} + +export interface SOCWrapperOptions { + client: SavedObjectsClient; + request: any; +} + +export type SOCWrapperFactory = (options: SOCWrapperOptions) => SavedObjectsClient; diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_wrapper_factory.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_wrapper_factory.js deleted file mode 100644 index da7a448d86d2d..0000000000000 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_wrapper_factory.js +++ /dev/null @@ -1,16 +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 { SpacesSavedObjectsClient } from './spaces_saved_objects_client'; - -export function spacesSavedObjectsClientWrapperFactory(spacesService, types) { - return ({ client, request }) => new SpacesSavedObjectsClient({ - baseClient: client, - request, - spacesService, - types, - }); -} diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_wrapper_factory.ts b/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_wrapper_factory.ts new file mode 100644 index 0000000000000..9e19556abd20c --- /dev/null +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_wrapper_factory.ts @@ -0,0 +1,22 @@ +/* + * 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 { SpacesService } from '../create_spaces_service'; +import { SOCWrapperOptions } from './saved_objects_client_types'; +import { SpacesSavedObjectsClient } from './spaces_saved_objects_client'; + +export function spacesSavedObjectsClientWrapperFactory( + spacesService: SpacesService, + types: string[] +) { + return ({ client, request }: SOCWrapperOptions) => + new SpacesSavedObjectsClient({ + baseClient: client, + request, + spacesService, + types, + }); +} diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.ts similarity index 80% rename from x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.js rename to x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.ts index fe9e363b01ce3..5a6ca30dba833 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.js +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.ts @@ -4,24 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SpacesSavedObjectsClient } from './spaces_saved_objects_client'; -import { createSpacesService } from '../create_spaces_service'; import { DEFAULT_SPACE_ID } from '../../../common/constants'; +import { Space } from '../../../common/model/space'; +import { createSpacesService } from '../create_spaces_service'; +import { SpacesSavedObjectsClient } from './spaces_saved_objects_client'; -const config = { - 'server.basePath': '/' +const config: any = { + 'server.basePath': '/', }; +const types = ['foo', 'bar', 'space']; + const server = { config: () => ({ - get: (key) => { + get: (key: string) => { return config[key]; - } - }) + }, + }), }; -const createMockRequest = (space) => ({ - getBasePath: () => space.id !== DEFAULT_SPACE_ID ? `/s/${space.id}` : '', +const createMockRequest = (space: Partial) => ({ + getBasePath: () => (space.id !== DEFAULT_SPACE_ID ? `/s/${space.id}` : ''), }); const createMockClient = () => { @@ -41,10 +44,9 @@ const createMockClient = () => { [ { id: DEFAULT_SPACE_ID, expectedNamespace: undefined }, - { id: 'space_1', expectedNamespace: 'space_1' } + { id: 'space_1', expectedNamespace: 'space_1' }, ].forEach(currentSpace => { describe(`${currentSpace.id} space`, () => { - describe('#get', () => { test(`throws error if options.namespace is specified`, async () => { const request = createMockRequest({ id: currentSpace.id }); @@ -55,9 +57,12 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); - await expect(client.get('foo', null, { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); + await expect( + client.get('foo', '', { namespace: 'bar' }) + ).rejects.toThrowErrorMatchingSnapshot(); }); test(`throws error if type is space`, async () => { @@ -69,9 +74,10 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); - await expect(client.get('space', null)).rejects.toThrowErrorMatchingSnapshot(); + await expect(client.get('space', '')).rejects.toThrowErrorMatchingSnapshot(); }); test(`supplements options with undefined namespace`, async () => { @@ -85,14 +91,19 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); const type = Symbol(); const id = Symbol(); const options = Object.freeze({ foo: 'bar' }); + // @ts-ignore const actualReturnValue = await client.get(type, id, options); expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.get).toHaveBeenCalledWith(type, id, { foo: 'bar', namespace: currentSpace.expectedNamespace }); + expect(baseClient.get).toHaveBeenCalledWith(type, id, { + foo: 'bar', + namespace: currentSpace.expectedNamespace, + }); }); }); @@ -106,9 +117,12 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); - await expect(client.bulkGet([{ type: 'foo' }], { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); + await expect( + client.bulkGet([{ id: '', type: 'foo' }], { namespace: 'bar' }) + ).rejects.toThrowErrorMatchingSnapshot(); }); test(`throws error if objects type is space`, async () => { @@ -120,9 +134,12 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); - await expect(client.bulkGet([{ type: 'foo' }, { type: 'space' }], { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); + await expect( + client.bulkGet([{ id: '', type: 'foo' }, { id: '', type: 'space' }], { namespace: 'bar' }) + ).rejects.toThrowErrorMatchingSnapshot(); }); test(`supplements options with undefined namespace`, async () => { @@ -136,14 +153,19 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); const objects = [{ type: 'foo' }]; const options = Object.freeze({ foo: 'bar' }); + // @ts-ignore const actualReturnValue = await client.bulkGet(objects, options); expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.bulkGet).toHaveBeenCalledWith(objects, { foo: 'bar', namespace: currentSpace.expectedNamespace }); + expect(baseClient.bulkGet).toHaveBeenCalledWith(objects, { + foo: 'bar', + namespace: currentSpace.expectedNamespace, + }); }); }); @@ -157,6 +179,7 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); await expect(client.find({ namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); @@ -173,6 +196,7 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); await expect(client.find({ type: 'space' })).rejects.toThrowErrorMatchingSnapshot(); @@ -189,13 +213,17 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); const options = Object.freeze({ type: 'foo' }); const actualReturnValue = await client.find(options); expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.find).toHaveBeenCalledWith({ type: ['foo'], namespace: currentSpace.expectedNamespace }); + expect(baseClient.find).toHaveBeenCalledWith({ + type: ['foo'], + namespace: currentSpace.expectedNamespace, + }); }); test(`throws error if options.type is array containing space`, async () => { @@ -209,9 +237,12 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); - await expect(client.find({ type: ['space', 'foo'] })).rejects.toThrowErrorMatchingSnapshot(); + await expect( + client.find({ type: ['space', 'foo'] }) + ).rejects.toThrowErrorMatchingSnapshot(); }); test(`if options.type isn't provided specifies options.type based on the types excluding the space`, async () => { @@ -220,7 +251,6 @@ const createMockClient = () => { const expectedReturnValue = Symbol(); baseClient.find.mockReturnValue(expectedReturnValue); const spacesService = createSpacesService(server); - const types = ['foo', 'bar', 'space']; const client = new SpacesSavedObjectsClient({ request, @@ -229,7 +259,9 @@ const createMockClient = () => { types, }); - await expect(client.find({ type: ['space', 'foo'] })).rejects.toThrowErrorMatchingSnapshot(); + await expect( + client.find({ type: ['space', 'foo'] }) + ).rejects.toThrowErrorMatchingSnapshot(); }); test(`supplements options with undefined namespace`, async () => { @@ -243,13 +275,17 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); const options = Object.freeze({ type: ['foo', 'bar'] }); const actualReturnValue = await client.find(options); expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.find).toHaveBeenCalledWith({ type: ['foo', 'bar'], namespace: currentSpace.expectedNamespace }); + expect(baseClient.find).toHaveBeenCalledWith({ + type: ['foo', 'bar'], + namespace: currentSpace.expectedNamespace, + }); }); }); @@ -263,10 +299,12 @@ const createMockClient = () => { request, baseClient, spacesService, - + types, }); - await expect(client.create('foo', {}, { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); + await expect( + client.create('foo', {}, { namespace: 'bar' }) + ).rejects.toThrowErrorMatchingSnapshot(); }); test(`throws error if type is space`, async () => { @@ -278,6 +316,7 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); await expect(client.create('space', {})).rejects.toThrowErrorMatchingSnapshot(); @@ -294,15 +333,20 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); const type = Symbol(); const attributes = Symbol(); const options = Object.freeze({ foo: 'bar' }); + // @ts-ignore const actualReturnValue = await client.create(type, attributes, options); expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.create).toHaveBeenCalledWith(type, attributes, { foo: 'bar', namespace: currentSpace.expectedNamespace }); + expect(baseClient.create).toHaveBeenCalledWith(type, attributes, { + foo: 'bar', + namespace: currentSpace.expectedNamespace, + }); }); }); @@ -316,9 +360,12 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); - await expect(client.bulkCreate([{ type: 'foo' }], { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); + await expect( + client.bulkCreate([{ id: '', type: 'foo', attributes: {} }], { namespace: 'bar' }) + ).rejects.toThrowErrorMatchingSnapshot(); }); test(`throws error if objects type is space`, async () => { @@ -330,9 +377,15 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); - await expect(client.bulkCreate([{ type: 'foo' }, { type: 'space' }])).rejects.toThrowErrorMatchingSnapshot(); + await expect( + client.bulkCreate([ + { id: '', type: 'foo', attributes: {} }, + { id: '', type: 'space', attributes: {} }, + ]) + ).rejects.toThrowErrorMatchingSnapshot(); }); test(`supplements options with undefined namespace`, async () => { @@ -346,14 +399,19 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); const objects = [{ type: 'foo' }]; const options = Object.freeze({ foo: 'bar' }); + // @ts-ignore const actualReturnValue = await client.bulkCreate(objects, options); expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.bulkCreate).toHaveBeenCalledWith(objects, { foo: 'bar', namespace: currentSpace.expectedNamespace }); + expect(baseClient.bulkCreate).toHaveBeenCalledWith(objects, { + foo: 'bar', + namespace: currentSpace.expectedNamespace, + }); }); }); @@ -367,9 +425,13 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); - await expect(client.update(null, null, null, { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); + await expect( + // @ts-ignore + client.update(null, null, null, { namespace: 'bar' }) + ).rejects.toThrowErrorMatchingSnapshot(); }); test(`throws error if type is space`, async () => { @@ -381,9 +443,10 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); - await expect(client.update('space', null)).rejects.toThrowErrorMatchingSnapshot(); + await expect(client.update('space', '', {})).rejects.toThrowErrorMatchingSnapshot(); }); test(`supplements options with undefined namespace`, async () => { @@ -397,16 +460,21 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); const type = Symbol(); const id = Symbol(); const attributes = Symbol(); const options = Object.freeze({ foo: 'bar' }); + // @ts-ignore const actualReturnValue = await client.update(type, id, attributes, options); expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.update).toHaveBeenCalledWith(type, id, attributes, { foo: 'bar', namespace: currentSpace.expectedNamespace }); + expect(baseClient.update).toHaveBeenCalledWith(type, id, attributes, { + foo: 'bar', + namespace: currentSpace.expectedNamespace, + }); }); }); @@ -420,9 +488,13 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); - await expect(client.delete(null, null, { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); + await expect( + // @ts-ignore + client.delete(null, null, { namespace: 'bar' }) + ).rejects.toThrowErrorMatchingSnapshot(); }); test(`throws error if type is space`, async () => { @@ -434,6 +506,7 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); await expect(client.delete('space', 'foo')).rejects.toThrowErrorMatchingSnapshot(); @@ -450,17 +523,21 @@ const createMockClient = () => { request, baseClient, spacesService, + types, }); const type = Symbol(); const id = Symbol(); const options = Object.freeze({ foo: 'bar' }); + // @ts-ignore const actualReturnValue = await client.delete(type, id, options); expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.delete).toHaveBeenCalledWith(type, id, { foo: 'bar', namespace: currentSpace.expectedNamespace }); + expect(baseClient.delete).toHaveBeenCalledWith(type, id, { + foo: 'bar', + namespace: currentSpace.expectedNamespace, + }); }); }); }); - }); diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts similarity index 61% rename from x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js rename to x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts index 92c9385cdda25..dc2131afee2b2 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts @@ -5,8 +5,26 @@ */ import { DEFAULT_SPACE_ID } from '../../../common/constants'; +import { SpacesService } from '../create_spaces_service'; +import { + BaseOptions, + BulkCreateObject, + BulkGetObjects, + CreateOptions, + FindOptions, + SavedObjectAttributes, + SavedObjectsClient, + UpdateOptions, +} from './saved_objects_client_types'; + +interface SpacesSavedObjectsClientOptions { + baseClient: SavedObjectsClient; + request: any; + spacesService: SpacesService; + types: string[]; +} -const coerceToArray = (param) => { +const coerceToArray = (param: string | string[]) => { if (Array.isArray(param)) { return param; } @@ -14,7 +32,7 @@ const coerceToArray = (param) => { return [param]; }; -const getNamespace = (spaceId) => { +const getNamespace = (spaceId: string) => { if (spaceId === DEFAULT_SPACE_ID) { return undefined; } @@ -22,37 +40,37 @@ const getNamespace = (spaceId) => { return spaceId; }; -const throwErrorIfNamespaceSpecified = (options) => { +const throwErrorIfNamespaceSpecified = (options: any) => { if (options.namespace) { throw new Error('Spaces currently determines the namespaces'); } }; -const throwErrorIfTypeIsSpace = (type) => { +const throwErrorIfTypeIsSpace = (type: string) => { if (type === 'space') { throw new Error('Spaces can not be accessed using the SavedObjectsClient'); } }; -const throwErrorIfTypesContainsSpace = (types) => { +const throwErrorIfTypesContainsSpace = (types: string[]) => { for (const type of types) { throwErrorIfTypeIsSpace(type); } }; -export class SpacesSavedObjectsClient { - constructor(options) { - const { - baseClient, - request, - spacesService, - types, - } = options; +export class SpacesSavedObjectsClient implements SavedObjectsClient { + public readonly errors: any; + private readonly client: SavedObjectsClient; + private readonly spaceId: string; + private readonly types: string[]; + + constructor(options: SpacesSavedObjectsClientOptions) { + const { baseClient, request, spacesService, types } = options; this.errors = baseClient.errors; - this._client = baseClient; - this._spaceId = spacesService.getSpaceId(request); - this._types = types; + this.client = baseClient; + this.spaceId = spacesService.getSpaceId(request); + this.types = types; } /** @@ -65,14 +83,14 @@ export class SpacesSavedObjectsClient { * @property {boolean} [options.overwrite=false] * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } - */ - async create(type, attributes = {}, options = {}) { + */ + public async create(type: string, attributes = {}, options: CreateOptions = {}) { throwErrorIfTypeIsSpace(type); throwErrorIfNamespaceSpecified(options); - return await this._client.create(type, attributes, { + return await this.client.create(type, attributes, { ...options, - namespace: getNamespace(this._spaceId) + namespace: getNamespace(this.spaceId), }); } @@ -85,13 +103,13 @@ export class SpacesSavedObjectsClient { * @property {string} [options.namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes, error: { message } }]} */ - async bulkCreate(objects, options = {}) { + public async bulkCreate(objects: BulkCreateObject[], options: BaseOptions = {}) { throwErrorIfTypesContainsSpace(objects.map(object => object.type)); throwErrorIfNamespaceSpecified(options); - return await this._client.bulkCreate(objects, { + return await this.client.bulkCreate(objects, { ...options, - namespace: getNamespace(this._spaceId) + namespace: getNamespace(this.spaceId), }); } @@ -104,13 +122,13 @@ export class SpacesSavedObjectsClient { * @property {string} [options.namespace] * @returns {promise} */ - async delete(type, id, options = {}) { + public async delete(type: string, id: string, options: BaseOptions = {}) { throwErrorIfTypeIsSpace(type); throwErrorIfNamespaceSpecified(options); - return await this._client.delete(type, id, { + return await this.client.delete(type, id, { ...options, - namespace: getNamespace(this._spaceId) + namespace: getNamespace(this.spaceId), }); } @@ -128,14 +146,19 @@ export class SpacesSavedObjectsClient { * @property {string} [options.namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - async find(options = {}) { - throwErrorIfTypesContainsSpace(coerceToArray(options.type)); + public async find(options: FindOptions = {}) { + if (options.type) { + throwErrorIfTypesContainsSpace(coerceToArray(options.type)); + } + throwErrorIfNamespaceSpecified(options); - return await this._client.find({ + return await this.client.find({ ...options, - type: (options.type ? coerceToArray(options.type) : this._types).filter(type => type !== 'space'), - namespace: getNamespace(this._spaceId) + type: (options.type ? coerceToArray(options.type) : this.types).filter( + type => type !== 'space' + ), + namespace: getNamespace(this.spaceId), }); } @@ -153,13 +176,13 @@ export class SpacesSavedObjectsClient { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet(objects = [], options = {}) { + public async bulkGet(objects: BulkGetObjects = [], options: BaseOptions = {}) { throwErrorIfTypesContainsSpace(objects.map(object => object.type)); throwErrorIfNamespaceSpecified(options); - return await this._client.bulkGet(objects, { + return await this.client.bulkGet(objects, { ...options, - namespace: getNamespace(this._spaceId) + namespace: getNamespace(this.spaceId), }); } @@ -172,13 +195,13 @@ export class SpacesSavedObjectsClient { * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } */ - async get(type, id, options = {}) { + public async get(type: string, id: string, options: BaseOptions = {}) { throwErrorIfTypeIsSpace(type); throwErrorIfNamespaceSpecified(options); - return await this._client.get(type, id, { + return await this.client.get(type, id, { ...options, - namespace: getNamespace(this._spaceId) + namespace: getNamespace(this.spaceId), }); } @@ -192,13 +215,18 @@ export class SpacesSavedObjectsClient { * @property {string} [options.namespace] * @returns {promise} */ - async update(type, id, attributes, options = {}) { + public async update( + type: string, + id: string, + attributes: SavedObjectAttributes, + options: UpdateOptions = {} + ) { throwErrorIfTypeIsSpace(type); throwErrorIfNamespaceSpecified(options); - return await this._client.update(type, id, attributes, { + return await this.client.update(type, id, attributes, { ...options, - namespace: getNamespace(this._spaceId) + namespace: getNamespace(this.spaceId), }); } } diff --git a/x-pack/plugins/spaces/server/lib/space_request_interceptors.test.js b/x-pack/plugins/spaces/server/lib/space_request_interceptors.test.ts similarity index 58% rename from x-pack/plugins/spaces/server/lib/space_request_interceptors.test.js rename to x-pack/plugins/spaces/server/lib/space_request_interceptors.test.ts index 0e7d47d2578cd..e99e98c9eb5ed 100644 --- a/x-pack/plugins/spaces/server/lib/space_request_interceptors.test.js +++ b/x-pack/plugins/spaces/server/lib/space_request_interceptors.test.ts @@ -3,46 +3,60 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import sinon from 'sinon'; +// @ts-ignore import { Server } from 'hapi'; +import sinon from 'sinon'; +import { SavedObject } from './saved_objects_client/saved_objects_client_types'; import { initSpacesRequestInterceptors } from './space_request_interceptors'; describe('interceptors', () => { const sandbox = sinon.sandbox.create(); - const teardowns = []; + const teardowns: Array<() => void> = []; const headers = { authorization: 'foo', }; - let server; - let request; + let server: any; + let request: any; beforeEach(() => { teardowns.push(() => sandbox.restore()); - request = async (path, setupFn = () => { }, testConfig = {}) => { - + request = async ( + path: string, + setupFn: (ser: any) => void = () => { + return; + }, + testConfig = {} + ) => { server = new Server(); server.connection({ port: 0 }); - const config = { + interface Config { + [key: string]: any; + } + const config: Config = { 'server.basePath': '/foo', ...testConfig, }; - server.decorate('server', 'config', jest.fn(() => { - return { - get: jest.fn(key => { - return config[key]; - }) - }; - })); + server.decorate( + 'server', + 'config', + jest.fn(() => { + return { + get: jest.fn(key => { + return config[key]; + }), + }; + }) + ); server.plugins = { spaces: { spacesClient: { getScopedClient: jest.fn(), - } - } + }, + }, }; initSpacesRequestInterceptors(server); @@ -50,9 +64,9 @@ describe('interceptors', () => { server.route({ method: 'GET', path: '/', - handler: (req, reply) => { + handler: (req: any, reply: any) => { return reply({ path: req.path, url: req.url, basePath: req.getBasePath() }); - } + }, }); await setupFn(server); @@ -77,12 +91,12 @@ describe('interceptors', () => { describe('onRequest', () => { test('handles paths without a space identifier', async () => { const testHandler = jest.fn((req, reply) => { - expect(req.path).toBe("/"); + expect(req.path).toBe('/'); return reply.continue(); }); - await request('/', (server) => { - server.ext('onRequest', testHandler); + await request('/', (hapiServer: any) => { + hapiServer.ext('onRequest', testHandler); }); expect(testHandler).toHaveBeenCalledTimes(1); @@ -90,12 +104,12 @@ describe('interceptors', () => { test('strips the Space URL Context from the request', async () => { const testHandler = jest.fn((req, reply) => { - expect(req.path).toBe("/"); + expect(req.path).toBe('/'); return reply.continue(); }); - await request('/s/foo', (server) => { - server.ext('onRequest', testHandler); + await request('/s/foo', (hapiServer: any) => { + hapiServer.ext('onRequest', testHandler); }); expect(testHandler).toHaveBeenCalledTimes(1); @@ -103,12 +117,12 @@ describe('interceptors', () => { test('ignores space identifiers in the middle of the path', async () => { const testHandler = jest.fn((req, reply) => { - expect(req.path).toBe("/some/path/s/foo/bar"); + expect(req.path).toBe('/some/path/s/foo/bar'); return reply.continue(); }); - await request('/some/path/s/foo/bar', (server) => { - server.ext('onRequest', testHandler); + await request('/some/path/s/foo/bar', (hapiServer: any) => { + hapiServer.ext('onRequest', testHandler); }); expect(testHandler).toHaveBeenCalledTimes(1); @@ -118,13 +132,13 @@ describe('interceptors', () => { const testHandler = jest.fn((req, reply) => { expect(req.path).toBe('/i/love/spaces.html'); expect(req.query).toEqual({ - queryParam: 'queryValue' + queryParam: 'queryValue', }); return reply.continue(); }); - await request('/s/foo/i/love/spaces.html?queryParam=queryValue', (server) => { - server.ext('onRequest', testHandler); + await request('/s/foo/i/love/spaces.html?queryParam=queryValue', (hapiServer: any) => { + hapiServer.ext('onRequest', testHandler); }); expect(testHandler).toHaveBeenCalledTimes(1); @@ -137,18 +151,18 @@ describe('interceptors', () => { const config = { 'server.basePath': serverBasePath, - 'server.defaultRoute': defaultRoute + 'server.defaultRoute': defaultRoute, }; - const setupTest = (server, spaces, testHandler) => { - server.plugins.spaces.spacesClient.getScopedClient.mockReturnValue({ + const setupTest = (hapiServer: any, spaces: SavedObject[], testHandler: any) => { + hapiServer.plugins.spaces.spacesClient.getScopedClient.mockReturnValue({ getAll() { return spaces; - } + }, }); // Register test inspector - server.ext('onPreResponse', testHandler); + hapiServer.ext('onPreResponse', testHandler); }; describe('with a single available space', () => { @@ -166,23 +180,32 @@ describe('interceptors', () => { return reply.continue(); }); - const spaces = [{ - id: 'a-space', - attributes: { - name: 'a space', - } - }]; - - await request('/', (server) => { - setupTest(server, spaces, testHandler); - }, config); + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, + }, + ]; + + await request( + '/', + (hapiServer: any) => { + setupTest(server, spaces, testHandler); + }, + config + ); expect(testHandler).toHaveBeenCalledTimes(1); - expect(server.plugins.spaces.spacesClient.getScopedClient).toHaveBeenCalledWith(expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization + expect(server.plugins.spaces.spacesClient.getScopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), }) - })); + ); }); test('it redirects to the defaultRoute within the context of the Default Space when navigating to Kibana root', async () => { @@ -203,42 +226,55 @@ describe('interceptors', () => { return reply.continue(); }); - const spaces = [{ - id: 'default', - attributes: { - name: 'Default Space', - } - }]; - - await request('/', (server) => { - setupTest(server, spaces, testHandler); - }, config); + const spaces = [ + { + id: 'default', + type: 'space', + attributes: { + name: 'Default Space', + }, + }, + ]; + + await request( + '/', + (hapiServer: any) => { + setupTest(hapiServer, spaces, testHandler); + }, + config + ); expect(testHandler).toHaveBeenCalledTimes(1); - expect(server.plugins.spaces.spacesClient.getScopedClient).toHaveBeenCalledWith(expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization + expect(server.plugins.spaces.spacesClient.getScopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), }) - })); + ); }); }); describe('with multiple available spaces', () => { test('it redirects to the Space Selector App when navigating to Kibana root', async () => { - - const spaces = [{ - id: 'a-space', - attributes: { - name: 'a space', - } - }, { - id: 'b-space', - attributes: { - name: 'b space', - } - }]; - - const getHiddenUiAppHandler = jest.fn(() => { return '
space selector
'; }); + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, + }, + { + id: 'b-space', + type: 'space', + attributes: { + name: 'b space', + }, + }, + ]; + + const getHiddenUiAppHandler = jest.fn(() => '
space selector
'); const testHandler = jest.fn((req, reply) => { const { response } = req; @@ -248,28 +284,35 @@ describe('interceptors', () => { } expect(response.statusCode).toEqual(200); - expect(response.source).toEqual({ "app": "
space selector
", "renderApp": true }); + expect(response.source).toEqual({ app: '
space selector
', renderApp: true }); return reply.continue(); }); - await request('/', (server) => { - server.decorate('server', 'getHiddenUiAppById', getHiddenUiAppHandler); - server.decorate('reply', 'renderApp', function (app) { - this({ renderApp: true, app }); - }); + await request( + '/', + (hapiServer: any) => { + server.decorate('server', 'getHiddenUiAppById', getHiddenUiAppHandler); + server.decorate('reply', 'renderApp', function renderAppHandler(app: any) { + // @ts-ignore + this({ renderApp: true, app }); + }); - setupTest(server, spaces, testHandler); - }, config); + setupTest(hapiServer, spaces, testHandler); + }, + config + ); expect(getHiddenUiAppHandler).toHaveBeenCalledTimes(1); expect(getHiddenUiAppHandler).toHaveBeenCalledWith('space_selector'); expect(testHandler).toHaveBeenCalledTimes(1); - expect(server.plugins.spaces.spacesClient.getScopedClient).toHaveBeenCalledWith(expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization + expect(server.plugins.spaces.spacesClient.getScopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), }) - })); + ); }); }); }); diff --git a/x-pack/plugins/spaces/server/lib/space_request_interceptors.js b/x-pack/plugins/spaces/server/lib/space_request_interceptors.ts similarity index 95% rename from x-pack/plugins/spaces/server/lib/space_request_interceptors.js rename to x-pack/plugins/spaces/server/lib/space_request_interceptors.ts index 2f461d5fdf7b4..3a6b0c18b05ed 100644 --- a/x-pack/plugins/spaces/server/lib/space_request_interceptors.js +++ b/x-pack/plugins/spaces/server/lib/space_request_interceptors.ts @@ -4,15 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { DEFAULT_SPACE_ID } from '../../common/constants'; import { wrapError } from './errors'; import { addSpaceIdToPath, getSpaceIdFromPath } from './spaces_url_parser'; -import { DEFAULT_SPACE_ID } from '../../common/constants'; - -export function initSpacesRequestInterceptors(server) { +export function initSpacesRequestInterceptors(server: any) { const serverBasePath = server.config().get('server.basePath'); - server.ext('onRequest', async function spacesOnRequestHandler(request, reply) { + server.ext('onRequest', async function spacesOnRequestHandler(request: any, reply: any) { const path = request.path; // If navigating within the context of a space, then we store the Space's URL Context on the request, @@ -38,7 +37,7 @@ export function initSpacesRequestInterceptors(server) { return reply.continue(); }); - server.ext('onPostAuth', async function spacesOnRequestHandler(request, reply) { + server.ext('onPostAuth', async function spacesOnRequestHandler(request: any, reply: any) { const path = request.path; const isRequestingKibanaRoot = path === '/'; @@ -68,10 +67,9 @@ export function initSpacesRequestInterceptors(server) { // render spaces selector instead of home page const app = server.getHiddenUiAppById('space_selector'); return reply.renderApp(app, { - spaces + spaces, }); } - } catch (error) { return reply(wrapError(error)); } @@ -79,6 +77,4 @@ export function initSpacesRequestInterceptors(server) { return reply.continue(); }); - - } diff --git a/x-pack/plugins/spaces/server/lib/spaces_url_parser.test.js b/x-pack/plugins/spaces/server/lib/spaces_url_parser.test.ts similarity index 93% rename from x-pack/plugins/spaces/server/lib/spaces_url_parser.test.js rename to x-pack/plugins/spaces/server/lib/spaces_url_parser.test.ts index 68234b3ba48fb..5878272c84924 100644 --- a/x-pack/plugins/spaces/server/lib/spaces_url_parser.test.js +++ b/x-pack/plugins/spaces/server/lib/spaces_url_parser.test.ts @@ -3,8 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { getSpaceIdFromPath, addSpaceIdToPath } from './spaces_url_parser'; import { DEFAULT_SPACE_ID } from '../../common/constants'; +import { addSpaceIdToPath, getSpaceIdFromPath } from './spaces_url_parser'; describe('getSpaceIdFromPath', () => { describe('without a serverBasePath defined', () => { @@ -32,7 +32,9 @@ describe('getSpaceIdFromPath', () => { test('it identifies the space url context following the server base path', () => { const basePath = `/server-base-path-here/s/my-awesome-space-lives-here`; - expect(getSpaceIdFromPath(basePath, '/server-base-path-here')).toEqual('my-awesome-space-lives-here'); + expect(getSpaceIdFromPath(basePath, '/server-base-path-here')).toEqual( + 'my-awesome-space-lives-here' + ); }); test('ignores space identifiers in the middle of the path', () => { @@ -57,7 +59,9 @@ describe('addSpaceIdToPath', () => { }); test('it appends the requested path to the end of the url context', () => { - expect(addSpaceIdToPath('/base', 'context', '/final/destination')).toEqual('/base/s/context/final/destination'); + expect(addSpaceIdToPath('/base', 'context', '/final/destination')).toEqual( + '/base/s/context/final/destination' + ); }); test('it throws an error when the requested path does not start with a slash', () => { diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 4a965fa793432..44b418ba975ec 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -1,17 +1,28 @@ { - "extends": "../tsconfig.json", - "include": [ - "common/**/*", - "server/**/*", - "plugins/**/*", - ], - "exclude": [ - "test/**/*" - ], - "compilerOptions": { - "types": [ - "node", - "jest" + "extends": "../tsconfig.json", + "include": [ + "common/**/*", + "server/**/*", + "plugins/**/*", + ], + "exclude": [ + "test/**/*" + ], + "compilerOptions": { + "paths": { + "ui/*": [ + "src/ui/public/*" + ], + "plugins/xpack_main/*": [ + "x-pack/plugins/xpack_main/public/*" + ], + "plugins/spaces/*": [ + "x-pack/plugins/spaces/public/*" ] - } -} + }, + "types": [ + "node", + "jest" + ] + } +} \ No newline at end of file diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index e5d5878c9e026..c5e29c0967199 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -162,6 +162,10 @@ version "23.3.1" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.1.tgz#a4319aedb071d478e6f407d1c4578ec8156829cf" +"@types/joi@^10.4.4": + version "10.6.4" + resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.4.tgz#0989d69e792a7db13e951852e6949df6787f113f" + "@types/loglevel@^1.5.3": version "1.5.3" resolved "https://registry.yarnpkg.com/@types/loglevel/-/loglevel-1.5.3.tgz#adfce55383edc5998a2170ad581b3e23d6adb5b8"