From 5c042b9ae804a4e0e4001cb2f2f0a76238ee0ab3 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Fri, 31 Aug 2018 09:49:36 -0400 Subject: [PATCH 1/5] handle space renaming and deleting --- x-pack/plugins/spaces/index.js | 5 +- .../spaces/public/lib/{index.js => index.ts} | 2 +- .../spaces/public/lib/spaces_manager.ts | 8 +- ...lete_modal.js => confirm_delete_modal.tsx} | 36 ++--- .../components/confirm_redirect_modal.tsx | 42 +++++ .../components/delete_spaces_button.js | 104 ------------ .../components/delete_spaces_button.tsx | 150 ++++++++++++++++++ .../components/{index.js => index.ts} | 0 .../views/management/{index.js => index.ts} | 13 +- .../public/views/management/page_routes.js | 12 +- .../spaces_grid/{index.js => index.ts} | 2 +- ...aces_grid_page.js => spaces_grid_page.tsx} | 136 +++++++++------- .../public/views/nav_control/nav_control.js | 9 +- .../views/nav_control/nav_control_popover.tsx | 60 ++++--- .../public/views/space_selector/index.js | 4 +- 15 files changed, 349 insertions(+), 234 deletions(-) rename x-pack/plugins/spaces/public/lib/{index.js => index.ts} (82%) rename x-pack/plugins/spaces/public/views/management/components/{confirm_delete_modal.js => confirm_delete_modal.tsx} (54%) create mode 100644 x-pack/plugins/spaces/public/views/management/components/confirm_redirect_modal.tsx delete mode 100644 x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.js create mode 100644 x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.tsx rename x-pack/plugins/spaces/public/views/management/components/{index.js => index.ts} (100%) rename x-pack/plugins/spaces/public/views/management/{index.js => index.ts} (93%) rename x-pack/plugins/spaces/public/views/management/spaces_grid/{index.js => index.ts} (82%) rename x-pack/plugins/spaces/public/views/management/spaces_grid/{spaces_grid_page.js => spaces_grid_page.tsx} (50%) diff --git a/x-pack/plugins/spaces/index.js b/x-pack/plugins/spaces/index.js index 480641cc792f2..07f79b6d1c9be 100644 --- a/x-pack/plugins/spaces/index.js +++ b/x-pack/plugins/spaces/index.js @@ -42,10 +42,11 @@ export const spaces = (kibana) => new kibana.Plugin({ hacks: [], mappings, home: ['plugins/spaces/register_feature'], - injectDefaultVars: function () { + injectDefaultVars: function (server) { return { spaces: [], - activeSpace: null + activeSpace: null, + rootBasePath: server.config().get('server.basePath'), }; }, replaceInjectedVars: async function (vars, request, server) { diff --git a/x-pack/plugins/spaces/public/lib/index.js b/x-pack/plugins/spaces/public/lib/index.ts similarity index 82% rename from x-pack/plugins/spaces/public/lib/index.js rename to x-pack/plugins/spaces/public/lib/index.ts index 0a22964efbca3..538dd77e053f5 100644 --- a/x-pack/plugins/spaces/public/lib/index.js +++ b/x-pack/plugins/spaces/public/lib/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { SpacesManager } from './spaces_manager'; \ No newline at end of file +export { SpacesManager } from './spaces_manager'; diff --git a/x-pack/plugins/spaces/public/lib/spaces_manager.ts b/x-pack/plugins/spaces/public/lib/spaces_manager.ts index a6b21f2fa5229..9e5384a25ef84 100644 --- a/x-pack/plugins/spaces/public/lib/spaces_manager.ts +++ b/x-pack/plugins/spaces/public/lib/spaces_manager.ts @@ -12,11 +12,13 @@ import { Space } from '../../common/model/space'; export class SpacesManager extends EventEmitter { private httpAgent: any; private baseUrl: any; + private rootBasePath: string; - constructor(httpAgent: any, chrome: any) { + constructor(httpAgent: any, chrome: any, rootBasePath: string) { super(); this.httpAgent = httpAgent; this.baseUrl = chrome.addBasePath(`/api/spaces/v1`); + this.rootBasePath = rootBasePath; } public async getSpaces(): Promise { @@ -54,6 +56,10 @@ export class SpacesManager extends EventEmitter { .catch(() => this._displayError()); } + public redirectToSpaceSelector() { + window.location.href = this.rootBasePath; + } + public async requestRefresh() { this.emit('request_refresh'); } diff --git a/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.js b/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx similarity index 54% rename from x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.js rename to x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx index 587f32911a383..761aad62ba17c 100644 --- a/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.js +++ b/x-pack/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx @@ -4,26 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore +import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; import React from 'react'; -import PropTypes from 'prop-types'; +import { Space } from '../../../../common/model/space'; -import { - EuiOverlayMask, - EuiConfirmModal, -} from '@elastic/eui'; +interface Props { + spaces: Space[]; + onCancel: () => void; + onConfirm: () => void; +} -export const ConfirmDeleteModal = (props) => { - const { - spaces - } = props; +export const ConfirmDeleteModal = (props: Props) => { + const { spaces } = props; - const buttonText = spaces.length > 1 - ? `Delete ${spaces.length} spaces` - : `Delete space`; + const buttonText = spaces.length > 1 ? `Delete ${spaces.length} spaces` : `Delete space`; - const bodyQuestion = spaces.length > 1 - ? `Are you sure you want to delete these ${spaces.length} spaces?` - : `Are you sure you want to delete this space?`; + const bodyQuestion = + spaces.length > 1 + ? `Are you sure you want to delete these ${spaces.length} spaces?` + : `Are you sure you want to delete this space?`; return ( @@ -42,9 +42,3 @@ export const ConfirmDeleteModal = (props) => { ); }; - -ConfirmDeleteModal.propTypes = { - spaces: PropTypes.array.isRequired, - onCancel: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired -}; diff --git a/x-pack/plugins/spaces/public/views/management/components/confirm_redirect_modal.tsx b/x-pack/plugins/spaces/public/views/management/components/confirm_redirect_modal.tsx new file mode 100644 index 0000000000000..03b371034b258 --- /dev/null +++ b/x-pack/plugins/spaces/public/views/management/components/confirm_redirect_modal.tsx @@ -0,0 +1,42 @@ +/* + * 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. + */ + +// @ts-ignore +import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import React from 'react'; +import { Space } from '../../../../common/model/space'; + +interface Props { + space?: Space; + onCancel: () => void; + onConfirm: () => void; +} + +export const ConfirmRedirectModal = (props: Props) => { + const { space } = props; + + const buttonText = `Delete current space`; + + const bodyQuestion = + `You are about to delete your current space ` + + `(${space.name}). If you continue, you will be redirected to select a different space.`; + + return ( + + +

{bodyQuestion}

+
+
+ ); +}; diff --git a/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.js b/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.js deleted file mode 100644 index 6cb6d596a1996..0000000000000 --- a/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.js +++ /dev/null @@ -1,104 +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 React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { ConfirmDeleteModal } from './confirm_delete_modal'; -import { - EuiButton -} from '@elastic/eui'; -import { toastNotifications } from 'ui/notify'; - -export class DeleteSpacesButton extends Component { - state = { - showConfirmModal: false - }; - - render() { - const numSpaces = this.props.spaces.length; - - const buttonText = numSpaces > 1 - ? `Delete ${numSpaces} spaces` - : `Delete space`; - - return ( - - - {buttonText} - - {this.getConfirmDeleteModal()} - - ); - } - - onDeleteClick = () => { - this.setState({ - showConfirmModal: true - }); - }; - - getConfirmDeleteModal = () => { - if (!this.state.showConfirmModal) { - return null; - } - - return ( - { - this.setState({ - showConfirmModal: false - }); - }} - onConfirm={this.deleteSpaces} - /> - ); - }; - - deleteSpaces = () => { - const { - spacesManager, - spaces, - spacesNavState, - } = this.props; - - const deleteOperations = spaces.map(space => spacesManager.deleteSpace(space)); - - Promise.all(deleteOperations) - .then(() => { - this.setState({ - showConfirmModal: false - }); - - const message = spaces.length > 1 - ? `Deleted ${spaces.length} spaces.` - : `Deleted "${spaces[0].name}" space.`; - - toastNotifications.addSuccess(message); - - if (this.props.onDelete) { - this.props.onDelete(); - } - - spacesNavState.refreshSpacesList(); - - }) - .catch(error => { - const { - message = '' - } = error.data || {}; - - toastNotifications.addDanger(`Error deleting space: ${message}`); - }); - }; -} - -DeleteSpacesButton.propTypes = { - spaces: PropTypes.array.isRequired, - spacesManager: PropTypes.object.isRequired, - spacesNavState: PropTypes.object.isRequired, - onDelete: PropTypes.func -}; diff --git a/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.tsx b/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.tsx new file mode 100644 index 0000000000000..fb2a1595352ab --- /dev/null +++ b/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.tsx @@ -0,0 +1,150 @@ +/* + * 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 { EuiButton } from '@elastic/eui'; +import React, { Component, Fragment } from 'react'; +import { toastNotifications } from 'ui/notify'; +import { Space } from '../../../../common/model/space'; +import { SpacesManager } from '../../../lib/spaces_manager'; +import { ConfirmDeleteModal } from './confirm_delete_modal'; +import { ConfirmRedirectModal } from './confirm_redirect_modal'; + +interface Props { + spaces: Space[]; + spacesManager: SpacesManager; + spacesNavState: any; + onDelete: () => void; +} + +interface State { + showConfirmDeleteModal: boolean; + showConfirmRedirectModal: boolean; +} + +export class DeleteSpacesButton extends Component { + public state = { + showConfirmDeleteModal: false, + showConfirmRedirectModal: false, + }; + + public render() { + const numSpaces = this.props.spaces.length; + + const buttonText = numSpaces > 1 ? `Delete ${numSpaces} spaces` : `Delete space`; + + return ( + + + {buttonText} + + {this.getConfirmDeleteModal()} + {this.getConfirmRedirectModal()} + + ); + } + + public onDeleteClick = () => { + this.setState({ + showConfirmDeleteModal: true, + }); + }; + + public getConfirmDeleteModal = () => { + if (!this.state.showConfirmDeleteModal) { + return null; + } + + const { spaces, spacesNavState } = this.props; + + const isDeletingCurrentSpace = !!this.locateCurrentSpace(); + + const performDelete = () => { + this.deleteSpaces(() => { + this.setState({ + showConfirmDeleteModal: false, + }); + + const message = + spaces.length > 1 + ? `Deleted ${spaces.length} spaces.` + : `Deleted "${spaces[0].name}" space.`; + + toastNotifications.addSuccess(message); + + if (this.props.onDelete) { + this.props.onDelete(); + } + + spacesNavState.refreshSpacesList(); + }); + }; + + const nextStep = isDeletingCurrentSpace ? this.showConfirmRedirectModal : performDelete; + + return ( + { + this.setState({ + showConfirmDeleteModal: false, + }); + }} + onConfirm={nextStep} + /> + ); + }; + + public showConfirmRedirectModal = () => { + this.setState({ + showConfirmDeleteModal: false, + showConfirmRedirectModal: true, + }); + }; + + public getConfirmRedirectModal = () => { + if (!this.state.showConfirmRedirectModal) { + return null; + } + + const performDelete = () => { + this.deleteSpaces(() => { + this.props.spacesManager.redirectToSpaceSelector(); + }); + }; + + return ( + { + this.setState({ + showConfirmRedirectModal: false, + }); + }} + onConfirm={performDelete} + /> + ); + }; + + public deleteSpaces = (onComplete: () => void) => { + const { spacesManager, spaces } = this.props; + + const deleteOperations = spaces.map(space => spacesManager.deleteSpace(space)); + + Promise.all(deleteOperations) + .then(onComplete) + .catch(error => { + const { message = '' } = error.data || {}; + + toastNotifications.addDanger(`Error deleting space: ${message}`); + }); + }; + + private locateCurrentSpace = () => { + return this.props.spaces.find( + space => space.id === this.props.spacesNavState.getActiveSpace().id + ); + }; +} diff --git a/x-pack/plugins/spaces/public/views/management/components/index.js b/x-pack/plugins/spaces/public/views/management/components/index.ts similarity index 100% rename from x-pack/plugins/spaces/public/views/management/components/index.js rename to x-pack/plugins/spaces/public/views/management/components/index.ts diff --git a/x-pack/plugins/spaces/public/views/management/index.js b/x-pack/plugins/spaces/public/views/management/index.ts similarity index 93% rename from x-pack/plugins/spaces/public/views/management/index.js rename to x-pack/plugins/spaces/public/views/management/index.ts index d3c161c89f174..4e91aca8ef130 100644 --- a/x-pack/plugins/spaces/public/views/management/index.js +++ b/x-pack/plugins/spaces/public/views/management/index.ts @@ -5,16 +5,16 @@ */ import 'plugins/spaces/views/management/page_routes'; -import routes from 'ui/routes'; - +// @ts-ignore import { management } from 'ui/management'; +// @ts-ignore +import routes from 'ui/routes'; const MANAGE_SPACES_KEY = 'manage_spaces'; routes.defaults(/\/management/, { resolve: { - spacesManagementSection: function () { - + spacesManagementSection() { function getKibanaSection() { return management.getSection('kibana'); } @@ -24,7 +24,6 @@ routes.defaults(/\/management/, { } function ensureSpagesRegistered() { - const kibanaSection = getKibanaSection(); if (!kibanaSection.hasItem(MANAGE_SPACES_KEY)) { @@ -40,6 +39,6 @@ routes.defaults(/\/management/, { deregisterSpaces(); ensureSpagesRegistered(); - } - } + }, + }, }); diff --git a/x-pack/plugins/spaces/public/views/management/page_routes.js b/x-pack/plugins/spaces/public/views/management/page_routes.js index 5a16ff72ffbcb..0188c7d35a44f 100644 --- a/x-pack/plugins/spaces/public/views/management/page_routes.js +++ b/x-pack/plugins/spaces/public/views/management/page_routes.js @@ -20,11 +20,11 @@ const reactRootNodeId = 'manageSpacesReactRoot'; routes.when('/management/spaces/list', { template, - controller: function ($scope, $http, chrome, spacesNavState) { + controller: function ($scope, $http, chrome, spacesNavState, rootBasePath) { $scope.$$postDigest(() => { const domNode = document.getElementById(reactRootNodeId); - const spacesManager = new SpacesManager($http, chrome); + const spacesManager = new SpacesManager($http, chrome, rootBasePath); render( { const domNode = document.getElementById(reactRootNodeId); - const spacesManager = new SpacesManager($http, chrome); + const spacesManager = new SpacesManager($http, chrome, rootBasePath); render( { const domNode = document.getElementById(reactRootNodeId); const { space } = $route.current.params; - const spacesManager = new SpacesManager($http, chrome); + const spacesManager = new SpacesManager($http, chrome, rootBasePath); render( { + public state = { selectedSpaces: [], spaces: [], - loading: true + loading: true, + error: null, }; - componentDidMount() { + public componentDidMount() { this.loadGrid(); } - render() { + public render() { return ( -

Spaces

+ +

Spaces

+
{this.getPrimaryActionButton()}
!isReservedSpace(space), - onSelectionChange: this.onSelectionChange + selectable: (space: Space) => !isReservedSpace(space), + onSelectionChange: this.onSelectionChange, }} pagination={true} search={true} loading={this.state.loading} - message={this.state.loading ? "loading..." : undefined} + message={this.state.loading ? 'loading...' : undefined} />
); } - getPrimaryActionButton() { + public getPrimaryActionButton() { if (this.state.selectedSpaces.length > 0) { return ( { window.location.hash = `#/management/spaces/create`; }}>Create new space + { + window.location.hash = `#/management/spaces/create`; + }} + > + Create new space + ); } - loadGrid = () => { - const { - spacesManager - } = this.props; + public loadGrid = () => { + const { spacesManager } = this.props; this.setState({ loading: true, selectedSpaces: [], - spaces: [] + spaces: [], }); - const setSpaces = (spaces) => { + const setSpaces = (spaces: Space[]) => { this.setState({ loading: false, spaces, }); }; - spacesManager.getSpaces() + spacesManager + .getSpaces() .then(spaces => { setSpaces(spaces); }) .catch(error => { this.setState({ loading: false, - error + error, }); }); }; - getColumnConfig() { - return [{ - field: 'name', - name: 'Space', - sortable: true, - render: (value, record) => { - return ( - { window.location.hash = `#/management/spaces/edit/${encodeURIComponent(record.id)}`; }}> - {value} - - ); - } - }, { - field: 'id', - name: 'Identifier', - sortable: true - }, { - field: 'description', - name: 'Description', - sortable: true - }]; + public getColumnConfig() { + return [ + { + field: 'name', + name: 'Space', + sortable: true, + render: (value: string, record: Space) => { + return ( + { + window.location.hash = `#/management/spaces/edit/${encodeURIComponent(record.id)}`; + }} + > + {value} + + ); + }, + }, + { + field: 'id', + name: 'Identifier', + sortable: true, + }, + { + field: 'description', + name: 'Description', + sortable: true, + }, + ]; } - onSelectionChange = (selectedSpaces) => { + public onSelectionChange = (selectedSpaces: Space[]) => { this.setState({ selectedSpaces }); }; } - -SpacesGridPage.propTypes = { - spacesManager: PropTypes.object.isRequired, - spacesNavState: PropTypes.object.isRequired, -}; 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 index 9dfcaf8ede84a..784506e360d97 100644 --- a/x-pack/plugins/spaces/public/views/nav_control/nav_control.js +++ b/x-pack/plugins/spaces/public/views/nav_control/nav_control.js @@ -25,10 +25,10 @@ const module = uiModules.get('spaces_nav', ['kibana']); let spacesManager; -module.controller('spacesNavController', ($scope, $http, chrome, activeSpace) => { +module.controller('spacesNavController', ($scope, $http, chrome, activeSpace, rootBasePath) => { const domNode = document.getElementById(`spacesNavReactRoot`); - spacesManager = new SpacesManager($http, chrome); + spacesManager = new SpacesManager($http, chrome, rootBasePath); render(, domNode); @@ -39,8 +39,11 @@ module.controller('spacesNavController', ($scope, $http, chrome, activeSpace) => }); -module.service('spacesNavState', () => { +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_popover.tsx b/x-pack/plugins/spaces/public/views/nav_control/nav_control_popover.tsx index ea7a2d0ca4465..6bd643de0f3f1 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 @@ -22,19 +22,22 @@ interface Props { } interface State { - isOpen: boolean; + showSpaceSelector: boolean; loading: boolean; - activeSpaceExists: boolean; + activeSpace: Space | null; spaces: Space[]; } export class NavControlPopover extends Component { - public state = { - isOpen: false, - loading: false, - activeSpaceExists: true, - spaces: [], - }; + constructor(props: Props) { + super(props); + this.state = { + showSpaceSelector: false, + loading: false, + activeSpace: props.activeSpace.space, + spaces: [], + }; + } public componentDidMount() { this.loadSpaces(); @@ -63,8 +66,8 @@ export class NavControlPopover extends Component { { const spaces = await spacesManager.getSpaces(); - let activeSpaceExists = this.state.activeSpaceExists; + // Update the active space definition, if it changed since the last load operation + let activeSpaceEntry: Space | null = activeSpace.space; + if (activeSpace.valid) { - activeSpaceExists = !!spaces.find(space => space.id === this.props.activeSpace.space.id); + activeSpaceEntry = spaces.find(space => space.id === this.props.activeSpace.space.id) || null; } this.setState({ spaces, - activeSpaceExists, - isOpen: this.state.isOpen || !activeSpaceExists, + activeSpace: activeSpaceEntry, loading: false, }); } private getActiveSpaceButton = () => { - const { activeSpace } = this.props; + const { activeSpace } = this.state; if (!activeSpace) { - return null; - } - - if (activeSpace.valid && activeSpace.space) { - return this.getButton( - , - activeSpace.space.name - ); - } else if (activeSpace.error) { return this.getButton( , 'error' ); } - return null; + return this.getButton( + , + (activeSpace as Space).name + ); }; private getButton = (linkIcon: JSX.Element, linkTitle: string) => { // Mimics the current angular-based navigation link return (
- +
{linkIcon}
{linkTitle}
@@ -130,20 +128,20 @@ export class NavControlPopover extends Component { ); }; - private togglePortal = () => { - const isOpening = !this.state.isOpen; + private toggleSpaceSelector = () => { + const isOpening = !this.state.showSpaceSelector; if (isOpening) { this.loadSpaces(); } this.setState({ - isOpen: !this.state.isOpen, + showSpaceSelector: !this.state.showSpaceSelector, }); }; - private closePortal = () => { + private closeSpaceSelector = () => { this.setState({ - isOpen: false, + showSpaceSelector: false, }); }; diff --git a/x-pack/plugins/spaces/public/views/space_selector/index.js b/x-pack/plugins/spaces/public/views/space_selector/index.js index 21bc86bee93c2..a1109ea8a95e5 100644 --- a/x-pack/plugins/spaces/public/views/space_selector/index.js +++ b/x-pack/plugins/spaces/public/views/space_selector/index.js @@ -16,10 +16,10 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { SpaceSelector } from './space_selector'; const module = uiModules.get('spaces_selector', []); -module.controller('spacesSelectorController', ($scope, $http, spaces) => { +module.controller('spacesSelectorController', ($scope, $http, spaces, rootBasePath) => { const domNode = document.getElementById('spaceSelectorRoot'); - const spacesManager = new SpacesManager($http, chrome); + const spacesManager = new SpacesManager($http, chrome, rootBasePath); render(, domNode); From fa98a929f5a982796db04d34e02354c47f7b5fcc Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 4 Sep 2018 11:49:19 -0400 Subject: [PATCH 2/5] address PR feedback --- x-pack/plugins/spaces/index.js | 2 +- x-pack/plugins/spaces/public/lib/spaces_manager.ts | 8 ++++---- .../spaces/public/views/management/page_routes.js | 12 ++++++------ .../spaces/public/views/nav_control/nav_control.js | 4 ++-- .../spaces/public/views/space_selector/index.js | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/spaces/index.js b/x-pack/plugins/spaces/index.js index 07f79b6d1c9be..f12b36ab0662e 100644 --- a/x-pack/plugins/spaces/index.js +++ b/x-pack/plugins/spaces/index.js @@ -46,7 +46,7 @@ export const spaces = (kibana) => new kibana.Plugin({ return { spaces: [], activeSpace: null, - rootBasePath: server.config().get('server.basePath'), + spaceSelectorURL: server.config().get('server.basePath') || '/', }; }, replaceInjectedVars: async function (vars, request, server) { diff --git a/x-pack/plugins/spaces/public/lib/spaces_manager.ts b/x-pack/plugins/spaces/public/lib/spaces_manager.ts index 9e5384a25ef84..15818936b6dc0 100644 --- a/x-pack/plugins/spaces/public/lib/spaces_manager.ts +++ b/x-pack/plugins/spaces/public/lib/spaces_manager.ts @@ -12,13 +12,13 @@ import { Space } from '../../common/model/space'; export class SpacesManager extends EventEmitter { private httpAgent: any; private baseUrl: any; - private rootBasePath: string; + private spaceSelectorURL: string; - constructor(httpAgent: any, chrome: any, rootBasePath: string) { + constructor(httpAgent: any, chrome: any, spaceSelectorURL: string) { super(); this.httpAgent = httpAgent; this.baseUrl = chrome.addBasePath(`/api/spaces/v1`); - this.rootBasePath = rootBasePath; + this.spaceSelectorURL = spaceSelectorURL; } public async getSpaces(): Promise { @@ -57,7 +57,7 @@ export class SpacesManager extends EventEmitter { } public redirectToSpaceSelector() { - window.location.href = this.rootBasePath; + window.location.href = this.spaceSelectorURL; } public async requestRefresh() { diff --git a/x-pack/plugins/spaces/public/views/management/page_routes.js b/x-pack/plugins/spaces/public/views/management/page_routes.js index 0188c7d35a44f..07f97439184fe 100644 --- a/x-pack/plugins/spaces/public/views/management/page_routes.js +++ b/x-pack/plugins/spaces/public/views/management/page_routes.js @@ -20,11 +20,11 @@ const reactRootNodeId = 'manageSpacesReactRoot'; routes.when('/management/spaces/list', { template, - controller: function ($scope, $http, chrome, spacesNavState, rootBasePath) { + controller: function ($scope, $http, chrome, spacesNavState, spaceSelectorURL) { $scope.$$postDigest(() => { const domNode = document.getElementById(reactRootNodeId); - const spacesManager = new SpacesManager($http, chrome, rootBasePath); + const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL); render( { const domNode = document.getElementById(reactRootNodeId); - const spacesManager = new SpacesManager($http, chrome, rootBasePath); + const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL); render( { const domNode = document.getElementById(reactRootNodeId); const { space } = $route.current.params; - const spacesManager = new SpacesManager($http, chrome, rootBasePath); + const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL); render( { +module.controller('spacesNavController', ($scope, $http, chrome, activeSpace, spaceSelectorURL) => { const domNode = document.getElementById(`spacesNavReactRoot`); - spacesManager = new SpacesManager($http, chrome, rootBasePath); + spacesManager = new SpacesManager($http, chrome, spaceSelectorURL); render(, domNode); diff --git a/x-pack/plugins/spaces/public/views/space_selector/index.js b/x-pack/plugins/spaces/public/views/space_selector/index.js index a1109ea8a95e5..210e8ac8d5f82 100644 --- a/x-pack/plugins/spaces/public/views/space_selector/index.js +++ b/x-pack/plugins/spaces/public/views/space_selector/index.js @@ -16,10 +16,10 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { SpaceSelector } from './space_selector'; const module = uiModules.get('spaces_selector', []); -module.controller('spacesSelectorController', ($scope, $http, spaces, rootBasePath) => { +module.controller('spacesSelectorController', ($scope, $http, spaces, spaceSelectorURL) => { const domNode = document.getElementById('spaceSelectorRoot'); - const spacesManager = new SpacesManager($http, chrome, rootBasePath); + const spacesManager = new SpacesManager($http, chrome, spaceSelectorURL); render(, domNode); From b1d8e875ed2e0a1ccdae4bde7e6f50c34793df65 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 4 Sep 2018 13:19:57 -0400 Subject: [PATCH 3/5] Enhanced confirmation when deleting a space. Removed bulk delete --- .../components/confirm_delete_modal.tsx | 138 ++++++++++++++---- .../components/confirm_redirect_modal.tsx | 42 ------ .../components/delete_spaces_button.tsx | 119 ++++++--------- .../edit_space/manage_space_page.js | 2 +- .../spaces_grid/spaces_grid_page.tsx | 59 +++----- 5 files changed, 172 insertions(+), 188 deletions(-) delete mode 100644 x-pack/plugins/spaces/public/views/management/components/confirm_redirect_modal.tsx 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 761aad62ba17c..acd70b8e1fb6d 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 @@ -4,41 +4,117 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; -import React from 'react'; +import { + EuiCallOut, + // @ts-ignore + EuiConfirmModal, + EuiFieldText, + EuiFormRow, + EuiOverlayMask, + EuiText, +} from '@elastic/eui'; +import React, { ChangeEvent, Component } from 'react'; import { Space } from '../../../../common/model/space'; +import { SpacesManager } from '../../../lib'; interface Props { - spaces: Space[]; + space: Space; + spacesManager: SpacesManager; + spacesNavState: any; onCancel: () => void; onConfirm: () => void; } -export const ConfirmDeleteModal = (props: Props) => { - const { spaces } = props; - - const buttonText = spaces.length > 1 ? `Delete ${spaces.length} spaces` : `Delete space`; - - const bodyQuestion = - spaces.length > 1 - ? `Are you sure you want to delete these ${spaces.length} spaces?` - : `Are you sure you want to delete this space?`; - - return ( - - -

{bodyQuestion}

-

This operation cannot be undone!

-
-
- ); -}; +interface State { + confirmSpaceName: string; + error: boolean | null; +} + +export class ConfirmDeleteModal extends Component { + public state = { + confirmSpaceName: '', + error: null, + }; + + public render() { + const { space, spacesNavState, onCancel } = this.props; + + let warning = null; + if (isDeletingCurrentSpace(space, spacesNavState)) { + const name = ( + + ({space.name}) + + ); + warning = ( + + + You are about to delete your current space {name}. You will be redirected to choose a + different space if you continue. + + + ); + } + + return ( + + +

+ Are you sure you want to delete the {space.name} space? +

+ + + + + + {warning} +
+
+ ); + } + + private onSpaceNameChange = (e: ChangeEvent) => { + if (typeof this.state.error === 'boolean') { + this.setState({ + confirmSpaceName: e.target.value, + error: e.target.value !== this.props.space.name, + }); + } else { + this.setState({ + confirmSpaceName: e.target.value, + }); + } + }; + + private onConfirm = async () => { + if (this.state.confirmSpaceName === this.props.space.name) { + const needsRedirect = isDeletingCurrentSpace(this.props.space, this.props.spacesNavState); + const spacesManager = this.props.spacesManager; + + await this.props.onConfirm(); + if (needsRedirect) { + spacesManager.redirectToSpaceSelector(); + } + } else { + this.setState({ + error: true, + }); + } + }; +} + +function isDeletingCurrentSpace(space: Space, spacesNavState: any) { + return space.id === spacesNavState.getActiveSpace().id; +} diff --git a/x-pack/plugins/spaces/public/views/management/components/confirm_redirect_modal.tsx b/x-pack/plugins/spaces/public/views/management/components/confirm_redirect_modal.tsx deleted file mode 100644 index 03b371034b258..0000000000000 --- a/x-pack/plugins/spaces/public/views/management/components/confirm_redirect_modal.tsx +++ /dev/null @@ -1,42 +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. - */ - -// @ts-ignore -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; -import React from 'react'; -import { Space } from '../../../../common/model/space'; - -interface Props { - space?: Space; - onCancel: () => void; - onConfirm: () => void; -} - -export const ConfirmRedirectModal = (props: Props) => { - const { space } = props; - - const buttonText = `Delete current space`; - - const bodyQuestion = - `You are about to delete your current space ` + - `(${space.name}). If you continue, you will be redirected to select a different space.`; - - return ( - - -

{bodyQuestion}

-
-
- ); -}; diff --git a/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.tsx b/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.tsx index fb2a1595352ab..8c0e868d2b5aa 100644 --- a/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.tsx +++ b/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.tsx @@ -4,16 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton } from '@elastic/eui'; +import { EuiButton, EuiButtonIcon, EuiButtonIconProps } from '@elastic/eui'; import React, { Component, Fragment } from 'react'; import { toastNotifications } from 'ui/notify'; import { Space } from '../../../../common/model/space'; import { SpacesManager } from '../../../lib/spaces_manager'; import { ConfirmDeleteModal } from './confirm_delete_modal'; -import { ConfirmRedirectModal } from './confirm_redirect_modal'; interface Props { - spaces: Space[]; + style?: 'button' | 'icon'; + space: Space; spacesManager: SpacesManager; spacesNavState: any; onDelete: () => void; @@ -31,17 +31,28 @@ export class DeleteSpacesButton extends Component { }; public render() { - const numSpaces = this.props.spaces.length; + const buttonText = `Delete space`; - const buttonText = numSpaces > 1 ? `Delete ${numSpaces} spaces` : `Delete space`; + let ButtonComponent: any = EuiButton; + + const extraProps: EuiButtonIconProps = {}; + + if (this.props.style === 'icon') { + ButtonComponent = EuiButtonIcon; + extraProps.iconType = 'trash'; + } return ( - + {buttonText} - + {this.getConfirmDeleteModal()} - {this.getConfirmRedirectModal()} ); } @@ -57,94 +68,46 @@ export class DeleteSpacesButton extends Component { return null; } - const { spaces, spacesNavState } = this.props; - - const isDeletingCurrentSpace = !!this.locateCurrentSpace(); - - const performDelete = () => { - this.deleteSpaces(() => { - this.setState({ - showConfirmDeleteModal: false, - }); - - const message = - spaces.length > 1 - ? `Deleted ${spaces.length} spaces.` - : `Deleted "${spaces[0].name}" space.`; - - toastNotifications.addSuccess(message); - - if (this.props.onDelete) { - this.props.onDelete(); - } - - spacesNavState.refreshSpacesList(); - }); - }; - - const nextStep = isDeletingCurrentSpace ? this.showConfirmRedirectModal : performDelete; + const { spacesNavState, spacesManager } = this.props; return ( { this.setState({ showConfirmDeleteModal: false, }); }} - onConfirm={nextStep} + onConfirm={this.deleteSpaces} /> ); }; - public showConfirmRedirectModal = () => { - this.setState({ - showConfirmDeleteModal: false, - showConfirmRedirectModal: true, - }); - }; - - public getConfirmRedirectModal = () => { - if (!this.state.showConfirmRedirectModal) { - return null; - } + public deleteSpaces = async () => { + const { spacesManager, space, spacesNavState } = this.props; - const performDelete = () => { - this.deleteSpaces(() => { - this.props.spacesManager.redirectToSpaceSelector(); - }); - }; + try { + await spacesManager.deleteSpace(space); + } catch (error) { + const { message: errorMessage = '' } = error.data || {}; - return ( - { - this.setState({ - showConfirmRedirectModal: false, - }); - }} - onConfirm={performDelete} - /> - ); - }; + toastNotifications.addDanger(`Error deleting space: ${errorMessage}`); + } - public deleteSpaces = (onComplete: () => void) => { - const { spacesManager, spaces } = this.props; + this.setState({ + showConfirmDeleteModal: false, + }); - const deleteOperations = spaces.map(space => spacesManager.deleteSpace(space)); + const message = `Deleted "${space.name}" space.`; - Promise.all(deleteOperations) - .then(onComplete) - .catch(error => { - const { message = '' } = error.data || {}; + toastNotifications.addSuccess(message); - toastNotifications.addDanger(`Error deleting space: ${message}`); - }); - }; + if (this.props.onDelete) { + this.props.onDelete(); + } - private locateCurrentSpace = () => { - return this.props.spaces.find( - space => space.id === this.props.spacesNavState.getActiveSpace().id - ); + spacesNavState.refreshSpacesList(); }; } 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.js index 606c7d9ad1c7a..ef03c2d08bf1b 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.js @@ -215,7 +215,7 @@ export class ManageSpacePage extends Component { return ( {

Spaces

- {this.getPrimaryActionButton()} @@ -66,10 +63,7 @@ export class SpacesGridPage extends Component { itemId={'id'} items={this.state.spaces} columns={this.getColumnConfig()} - selection={{ - selectable: (space: Space) => !isReservedSpace(space), - onSelectionChange: this.onSelectionChange, - }} + hasActions pagination={true} search={true} loading={this.state.loading} @@ -80,36 +74,11 @@ export class SpacesGridPage extends Component { ); } - public getPrimaryActionButton() { - if (this.state.selectedSpaces.length > 0) { - return ( - - ); - } - - return ( - { - window.location.hash = `#/management/spaces/create`; - }} - > - Create new space - - ); - } - public loadGrid = () => { const { spacesManager } = this.props; this.setState({ loading: true, - selectedSpaces: [], spaces: [], }); @@ -161,10 +130,28 @@ export class SpacesGridPage extends Component { name: 'Description', sortable: true, }, + { + name: 'Actions', + actions: [ + { + render: (record: any) => { + if (isReservedSpace(record)) { + return ; + } + + return ( + + ); + }, + }, + ], + }, ]; } - - public onSelectionChange = (selectedSpaces: Space[]) => { - this.setState({ selectedSpaces }); - }; } From c1811df76ca99d638413392cce7640cc5cf6acd3 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 4 Sep 2018 15:37:09 -0400 Subject: [PATCH 4/5] bring back the Create Space button --- .../management/spaces_grid/spaces_grid_page.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 fb4bdece2bce1..32c39c0cf35cc 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 @@ -7,6 +7,7 @@ import React, { Component } from 'react'; import { + EuiButton, EuiFlexGroup, EuiFlexItem, // @ts-ignore @@ -56,6 +57,7 @@ export class SpacesGridPage extends Component {

Spaces

+ {this.getPrimaryActionButton()} @@ -74,6 +76,19 @@ export class SpacesGridPage extends Component { ); } + public getPrimaryActionButton() { + return ( + { + window.location.hash = `#/management/spaces/create`; + }} + > + Create new space + + ); + } + public loadGrid = () => { const { spacesManager } = this.props; From aa8325fef4d5a29e8b6cf9d52237296ddac7d3d6 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Thu, 6 Sep 2018 08:02:56 -0400 Subject: [PATCH 5/5] additional PR feedback --- .../components/confirm_delete_modal.tsx | 9 +- .../views/management/components/index.ts | 2 +- .../delete_spaces_button.tsx | 2 +- .../edit_space/manage_space_page.js | 2 +- .../views/management/manage_spaces.less | 4 +- .../spaces_grid/spaces_grid_page.tsx | 130 ++++++++++++++---- 6 files changed, 115 insertions(+), 34 deletions(-) rename x-pack/plugins/spaces/public/views/management/{components => edit_space}/delete_spaces_button.tsx (97%) 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 acd70b8e1fb6d..0810243a1d035 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 @@ -64,17 +64,18 @@ export class ConfirmDeleteModal extends Component { confirmButtonText={'Delete space'} onCancel={onCancel} onConfirm={this.onConfirm} - title={`Confirm Delete`} + title={`Delete space '${space.name}'`} defaultFocusedButton={'cancel'} >

- Are you sure you want to delete the {space.name} space? + Deleting a space permanently removes the space and all of its contents. You can't undo + this action.

diff --git a/x-pack/plugins/spaces/public/views/management/components/index.ts b/x-pack/plugins/spaces/public/views/management/components/index.ts index e134461ee43a6..651455d00e9f2 100644 --- a/x-pack/plugins/spaces/public/views/management/components/index.ts +++ b/x-pack/plugins/spaces/public/views/management/components/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { DeleteSpacesButton } from './delete_spaces_button'; +export { ConfirmDeleteModal } from './confirm_delete_modal'; diff --git a/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.tsx b/x-pack/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx similarity index 97% rename from x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.tsx rename to x-pack/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx index 8c0e868d2b5aa..e255375baf85b 100644 --- a/x-pack/plugins/spaces/public/views/management/components/delete_spaces_button.tsx +++ b/x-pack/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx @@ -9,7 +9,7 @@ import React, { Component, Fragment } from 'react'; import { toastNotifications } from 'ui/notify'; import { Space } from '../../../../common/model/space'; import { SpacesManager } from '../../../lib/spaces_manager'; -import { ConfirmDeleteModal } from './confirm_delete_modal'; +import { ConfirmDeleteModal } from '../components/confirm_delete_modal'; interface Props { style?: 'button' | 'icon'; 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.js index ef03c2d08bf1b..6980a992199d8 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.js @@ -24,7 +24,7 @@ import { EuiLoadingSpinner, } from '@elastic/eui'; -import { DeleteSpacesButton } from '../components'; +import { DeleteSpacesButton } from './delete_spaces_button'; import { SpaceAvatar } from '../../../components'; import { Notifier, toastNotifications } from 'ui/notify'; diff --git a/x-pack/plugins/spaces/public/views/management/manage_spaces.less b/x-pack/plugins/spaces/public/views/management/manage_spaces.less index 4254ed9081002..15f085df6d1b6 100644 --- a/x-pack/plugins/spaces/public/views/management/manage_spaces.less +++ b/x-pack/plugins/spaces/public/views/management/manage_spaces.less @@ -1,4 +1,4 @@ -.manageSpaces__application, .manageSpaces__.euiPanel, #manageSpacesReactRoot, .editSpacePage, .editSpacePage__content { +.manageSpaces__application, .manageSpaces__.euiPanel, #manageSpacesReactRoot { background: #f5f5f5; } @@ -10,7 +10,7 @@ padding: 0; } -.manageSpacePage { +.manageSpacePage, .spacesGridPage { min-height: ~"calc(100vh - 70px)"; } 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 32c39c0cf35cc..d6c06ffe1870a 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,10 +19,13 @@ import { EuiText, } from '@elastic/eui'; +import { toastNotifications } from 'ui/notify'; + import { isReservedSpace } from '../../../../common'; import { Space } from '../../../../common/model/space'; +import { SpaceAvatar } from '../../../components'; import { SpacesManager } from '../../../lib/spaces_manager'; -import { DeleteSpacesButton } from '../components'; +import { ConfirmDeleteModal } from '../components/confirm_delete_modal'; interface Props { spacesManager: SpacesManager; @@ -32,16 +35,22 @@ interface Props { interface State { spaces: Space[]; loading: boolean; + showConfirmDeleteModal: boolean; + selectedSpace: Space | null; error: Error | null; } export class SpacesGridPage extends Component { - public state = { - selectedSpaces: [], - spaces: [], - loading: true, - error: null, - }; + constructor(props: Props) { + super(props); + this.state = { + spaces: [], + loading: true, + showConfirmDeleteModal: false, + selectedSpace: null, + error: null, + }; + } public componentDidMount() { this.loadGrid(); @@ -49,7 +58,7 @@ export class SpacesGridPage extends Component { public render() { return ( - + @@ -72,6 +81,7 @@ export class SpacesGridPage extends Component { message={this.state.loading ? 'loading...' : undefined} /> + {this.getConfirmDeleteModal()} ); } @@ -89,6 +99,58 @@ export class SpacesGridPage extends Component { ); } + public getConfirmDeleteModal = () => { + if (!this.state.showConfirmDeleteModal || !this.state.selectedSpace) { + return null; + } + + const { spacesNavState, spacesManager } = this.props; + + return ( + { + this.setState({ + showConfirmDeleteModal: false, + }); + }} + onConfirm={this.deleteSpace} + /> + ); + }; + + public deleteSpace = async () => { + const { spacesManager, spacesNavState } = this.props; + + const space = this.state.selectedSpace; + + if (!space) { + return; + } + + try { + await spacesManager.deleteSpace(space); + } catch (error) { + const { message: errorMessage = '' } = error.data || {}; + + toastNotifications.addDanger(`Error deleting space: ${errorMessage}`); + } + + this.setState({ + showConfirmDeleteModal: false, + }); + + this.loadGrid(); + + const message = `Deleted "${space.name}" space.`; + + toastNotifications.addSuccess(message); + + spacesNavState.refreshSpacesList(); + }; + public loadGrid = () => { const { spacesManager } = this.props; @@ -127,10 +189,17 @@ export class SpacesGridPage extends Component { return ( { - window.location.hash = `#/management/spaces/edit/${encodeURIComponent(record.id)}`; + this.onEditSpaceClick(record); }} > - {value} + + + + + + {record.name} + + ); }, @@ -149,24 +218,35 @@ export class SpacesGridPage extends Component { name: 'Actions', actions: [ { - render: (record: any) => { - if (isReservedSpace(record)) { - return ; - } - - return ( - - ); - }, + name: 'Edit', + description: 'Edit this space.', + onClick: this.onEditSpaceClick, + type: 'icon', + icon: 'pencil', + color: 'primary', + }, + { + available: (record: Space) => !isReservedSpace(record), + name: 'Delete', + description: 'Delete this space.', + onClick: this.onDeleteSpaceClick, + type: 'icon', + icon: 'trash', + color: 'danger', }, ], }, ]; } + + private onEditSpaceClick = (space: Space) => { + window.location.hash = `#/management/spaces/edit/${encodeURIComponent(space.id)}`; + }; + + private onDeleteSpaceClick = (space: Space) => { + this.setState({ + selectedSpace: space, + showConfirmDeleteModal: true, + }); + }; }