diff --git a/x-pack/plugins/canvas/public/apps/workpad/routes.js b/x-pack/plugins/canvas/public/apps/workpad/routes.js index 98607304ba1ab..1efe744f36889 100644 --- a/x-pack/plugins/canvas/public/apps/workpad/routes.js +++ b/x-pack/plugins/canvas/public/apps/workpad/routes.js @@ -11,6 +11,7 @@ import { setWorkpad } from '../../state/actions/workpad'; import { setAssets, resetAssets } from '../../state/actions/assets'; import { gotoPage } from '../../state/actions/pages'; import { getWorkpad } from '../../state/selectors/workpad'; +import { setCanUserWrite } from '../../state/actions/transient'; import { WorkpadApp } from './workpad_app'; export const routes = [ @@ -29,6 +30,10 @@ export const routes = [ router.redirectTo('loadWorkpad', { id: newWorkpad.id, page: 1 }); } catch (err) { notify.error(err, { title: `Couldn't create workpad` }); + // TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced + // https://github.com/elastic/kibana/issues/20277 + if (err.response.status === 403) dispatch(setCanUserWrite(false)); + router.redirectTo('home'); } }, meta: { @@ -48,6 +53,13 @@ export const routes = [ const { assets, ...workpad } = fetchedWorkpad; dispatch(setWorkpad(workpad)); dispatch(setAssets(assets)); + + // tests if user has permissions to write to workpads + // TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced + // https://github.com/elastic/kibana/issues/20277 + workpadService.update(params.id, fetchedWorkpad).catch(err => { + if (err.response.status === 403) dispatch(setCanUserWrite(false)); + }); } catch (err) { notify.error(err, { title: `Couldn't load workpad with ID` }); return router.redirectTo('home'); diff --git a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/index.js b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/index.js index 90db6af85154e..119c071e3866d 100644 --- a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/index.js +++ b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/index.js @@ -8,8 +8,8 @@ import { connect } from 'react-redux'; import { compose, branch, renderComponent } from 'recompose'; import { initializeWorkpad } from '../../../state/actions/workpad'; import { selectElement } from '../../../state/actions/transient'; -import { getEditing, getAppReady } from '../../../state/selectors/app'; -import { getWorkpad } from '../../../state/selectors/workpad'; +import { canUserWrite, getAppReady } from '../../../state/selectors/app'; +import { getWorkpad, isWriteable } from '../../../state/selectors/workpad'; import { LoadWorkpad } from './load_workpad'; import { WorkpadApp as Component } from './workpad_app'; @@ -17,7 +17,7 @@ const mapStateToProps = state => { const appReady = getAppReady(state); return { - editing: getEditing(state), + isWriteable: isWriteable(state) && canUserWrite(state), appReady: typeof appReady === 'object' ? appReady : { ready: appReady }, workpad: getWorkpad(state), }; diff --git a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.js b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.js index 1d5dcc5d20553..49ef7ed97e07f 100644 --- a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.js +++ b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.js @@ -13,7 +13,7 @@ import { WorkpadHeader } from '../../../components/workpad_header'; export class WorkpadApp extends React.PureComponent { static propTypes = { - editing: PropTypes.bool, + isWriteable: PropTypes.bool.isRequired, deselectElement: PropTypes.func, initializeWorkpad: PropTypes.func.isRequired, }; @@ -23,7 +23,7 @@ export class WorkpadApp extends React.PureComponent { } render() { - const { editing, deselectElement } = this.props; + const { isWriteable, deselectElement } = this.props; return (
@@ -42,18 +42,16 @@ export class WorkpadApp extends React.PureComponent {
- {editing && ( + {isWriteable && (
)} - {editing ? ( -
- -
- ) : null} +
+ +
); diff --git a/x-pack/plugins/canvas/public/components/element_wrapper/index.js b/x-pack/plugins/canvas/public/components/element_wrapper/index.js index b932b3495db98..cbea3b487a4ab 100644 --- a/x-pack/plugins/canvas/public/components/element_wrapper/index.js +++ b/x-pack/plugins/canvas/public/components/element_wrapper/index.js @@ -6,14 +6,13 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { getEditing } from '../../state/selectors/app'; -import { getResolvedArgs, getSelectedPage } from '../../state/selectors/workpad'; +import { getResolvedArgs, getSelectedPage, isWriteable } from '../../state/selectors/workpad'; import { getState, getValue, getError } from '../../lib/resolved_arg'; import { ElementWrapper as Component } from './element_wrapper'; import { createHandlers as createHandlersWithDispatch } from './lib/handlers'; const mapStateToProps = (state, { element }) => ({ - isEditing: getEditing(state), + isWriteable: isWriteable(state), resolvedArg: getResolvedArgs(state, element.id, 'expressionRenderable'), selectedPage: getSelectedPage(state), }); diff --git a/x-pack/plugins/canvas/public/components/page_manager/index.js b/x-pack/plugins/canvas/public/components/page_manager/index.js index f4f7cad4431c7..7fd393d4d4068 100644 --- a/x-pack/plugins/canvas/public/components/page_manager/index.js +++ b/x-pack/plugins/canvas/public/components/page_manager/index.js @@ -7,10 +7,12 @@ import { connect } from 'react-redux'; import { compose, withState } from 'recompose'; import * as pageActions from '../../state/actions/pages'; -import { getSelectedPage, getWorkpad, getPages } from '../../state/selectors/workpad'; +import { canUserWrite } from '../../state/selectors/app'; +import { getSelectedPage, getWorkpad, getPages, isWriteable } from '../../state/selectors/workpad'; import { PageManager as Component } from './page_manager'; const mapStateToProps = state => ({ + isWriteable: isWriteable(state) && canUserWrite(state), pages: getPages(state), selectedPage: getSelectedPage(state), workpadId: getWorkpad(state).id, diff --git a/x-pack/plugins/canvas/public/components/page_manager/page_manager.js b/x-pack/plugins/canvas/public/components/page_manager/page_manager.js index 02d662fd91987..66c2196c0f900 100644 --- a/x-pack/plugins/canvas/public/components/page_manager/page_manager.js +++ b/x-pack/plugins/canvas/public/components/page_manager/page_manager.js @@ -14,6 +14,7 @@ import { PagePreview } from '../page_preview'; export class PageManager extends React.PureComponent { static propTypes = { + isWriteable: PropTypes.bool.isRequired, pages: PropTypes.array.isRequired, workpadId: PropTypes.string.isRequired, addPage: PropTypes.func.isRequired, @@ -102,11 +103,11 @@ export class PageManager extends React.PureComponent { }; renderPage = (page, i) => { - const { selectedPage, workpadId, movePage, duplicatePage } = this.props; + const { isWriteable, selectedPage, workpadId, movePage, duplicatePage } = this.props; const pageNumber = i + 1; return ( - + {provided => (
- - - - - + {isWriteable && ( + + + + + + )} div { @@ -26,6 +19,18 @@ } } + .canvasPageManager__pageList { + @include euiScrollBar; + display: flex; + overflow-x: auto; + overflow-y: hidden; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + } + .canvasPageManager__addPage { width: $euiSizeXXL + $euiSize; background: $euiColorSecondary; @@ -33,7 +38,6 @@ opacity: 0; animation: buttonPop $euiAnimSpeedNormal $euiAnimSlightResistance; animation-fill-mode: forwards; - height: 144px; } .canvasPageManager__addPageTip { diff --git a/x-pack/plugins/canvas/public/components/page_preview/page_preview.js b/x-pack/plugins/canvas/public/components/page_preview/page_preview.js index dfcbc31c54fa2..a69e25e115c33 100644 --- a/x-pack/plugins/canvas/public/components/page_preview/page_preview.js +++ b/x-pack/plugins/canvas/public/components/page_preview/page_preview.js @@ -9,22 +9,32 @@ import PropTypes from 'prop-types'; import { DomPreview } from '../dom_preview'; import { PageControls } from './page_controls'; -export const PagePreview = ({ page, pageNumber, height, duplicatePage, confirmDelete }) => ( +export const PagePreview = ({ + isWriteable, + page, + pageNumber, + height, + duplicatePage, + confirmDelete, +}) => (
- + {isWriteable && ( + + )}
); PagePreview.propTypes = { + isWriteable: PropTypes.bool.isRequired, page: PropTypes.shape({ id: PropTypes.string.isRequired, style: PropTypes.shape({ diff --git a/x-pack/plugins/canvas/public/components/toolbar/index.js b/x-pack/plugins/canvas/public/components/toolbar/index.js index a533e032ff765..bc873b6f2854c 100644 --- a/x-pack/plugins/canvas/public/components/toolbar/index.js +++ b/x-pack/plugins/canvas/public/components/toolbar/index.js @@ -7,7 +7,6 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { compose, withState, getContext, withHandlers } from 'recompose'; -import { getEditing } from '../../state/selectors/app'; import { getWorkpad, @@ -19,7 +18,6 @@ import { import { Toolbar as Component } from './toolbar'; const mapStateToProps = state => ({ - editing: getEditing(state), workpadName: getWorkpadName(state), workpadId: getWorkpad(state).id, totalPages: getWorkpad(state).pages.length, diff --git a/x-pack/plugins/canvas/public/components/toolbar/toolbar.js b/x-pack/plugins/canvas/public/components/toolbar/toolbar.js index 0427016a47a85..e742cbdc53ffb 100644 --- a/x-pack/plugins/canvas/public/components/toolbar/toolbar.js +++ b/x-pack/plugins/canvas/public/components/toolbar/toolbar.js @@ -24,7 +24,6 @@ import { Tray } from './tray'; export const Toolbar = props => { const { - editing, selectedElement, tray, setTray, @@ -63,7 +62,7 @@ export const Toolbar = props => { expression: !elementIsSelected ? null : , }; - return !editing ? null : ( + return (
{trays[tray] && {trays[tray]}} @@ -122,7 +121,6 @@ export const Toolbar = props => { Toolbar.propTypes = { workpadName: PropTypes.string, - editing: PropTypes.bool, tray: PropTypes.node, setTray: PropTypes.func.isRequired, nextPage: PropTypes.func.isRequired, diff --git a/x-pack/plugins/canvas/public/components/workpad/index.js b/x-pack/plugins/canvas/public/components/workpad/index.js index cc6c3ff124615..f99af9679c4b0 100644 --- a/x-pack/plugins/canvas/public/components/workpad/index.js +++ b/x-pack/plugins/canvas/public/components/workpad/index.js @@ -10,7 +10,7 @@ import { compose, withState, withProps, getContext, withHandlers } from 'recompo import { transitionsRegistry } from '../../lib/transitions_registry'; import { undoHistory, redoHistory } from '../../state/actions/history'; import { fetchAllRenderables } from '../../state/actions/elements'; -import { getFullscreen, getEditing } from '../../state/selectors/app'; +import { getFullscreen } from '../../state/selectors/app'; import { getSelectedPageIndex, getAllElements, @@ -25,7 +25,6 @@ const mapStateToProps = state => ({ totalElementCount: getAllElements(state).length, workpad: getWorkpad(state), isFullscreen: getFullscreen(state), - isEditing: getEditing(state), }); const mapDispatchToProps = { diff --git a/x-pack/plugins/canvas/public/components/workpad_header/index.js b/x-pack/plugins/canvas/public/components/workpad_header/index.js index 1941fc4bcf3fc..fec9c874744f3 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_header/index.js @@ -6,22 +6,23 @@ import { compose, withState } from 'recompose'; import { connect } from 'react-redux'; -import { getEditing } from '../../state/selectors/app'; -import { getWorkpadName, getSelectedPage } from '../../state/selectors/workpad'; -import { setEditing } from '../../state/actions/transient'; +import { canUserWrite } from '../../state/selectors/app'; +import { getWorkpadName, getSelectedPage, isWriteable } from '../../state/selectors/workpad'; +import { setWriteable } from '../../state/actions/workpad'; import { getAssets } from '../../state/selectors/assets'; import { addElement } from '../../state/actions/elements'; import { WorkpadHeader as Component } from './workpad_header'; const mapStateToProps = state => ({ - editing: getEditing(state), + isWriteable: isWriteable(state) && canUserWrite(state), + canUserWrite: canUserWrite(state), workpadName: getWorkpadName(state), selectedPage: getSelectedPage(state), hasAssets: Object.keys(getAssets(state)).length ? true : false, }); const mapDispatchToProps = dispatch => ({ - setEditing: editing => dispatch(setEditing(editing)), + setWriteable: isWriteable => dispatch(setWriteable(isWriteable)), addElement: pageId => partialElement => dispatch(addElement(pageId, partialElement)), }); @@ -30,7 +31,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => ({ ...dispatchProps, ...ownProps, addElement: dispatchProps.addElement(stateProps.selectedPage), - toggleEditing: () => dispatchProps.setEditing(!stateProps.editing), + toggleWriteable: () => dispatchProps.setWriteable(!stateProps.isWriteable), }); export const WorkpadHeader = compose( diff --git a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.js b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.js index adfb9d7a02641..bae28b72416d9 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.js +++ b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.js @@ -24,15 +24,16 @@ import { FullscreenControl } from '../fullscreen_control'; import { RefreshControl } from '../refresh_control'; export const WorkpadHeader = ({ - editing, - toggleEditing, + isWriteable, + canUserWrite, + toggleWriteable, hasAssets, addElement, setShowElementModal, showElementModal, }) => { const keyHandler = action => { - if (action === 'EDITING') toggleEditing(); + if (action === 'EDITING') toggleWriteable(); }; const elementAdd = ( @@ -58,6 +59,11 @@ export const WorkpadHeader = ({ ); + let readOnlyToolTip = ''; + + if (!canUserWrite) readOnlyToolTip = "You don't have permission to edit this workpad"; + else readOnlyToolTip = isWriteable ? 'Hide editing controls' : 'Show editing controls'; + return (
{showElementModal ? elementAdd : null} @@ -84,24 +90,24 @@ export const WorkpadHeader = ({ - - + {!canUserWrite && ( + + )} + { - toggleEditing(); + toggleWriteable(); }} size="s" - aria-label={editing ? 'Hide editing controls' : 'Show editing controls'} + aria-label={readOnlyToolTip} + isDisabled={!canUserWrite} /> - {editing ? ( + {isWriteable ? ( {hasAssets && ( @@ -128,8 +134,8 @@ export const WorkpadHeader = ({ }; WorkpadHeader.propTypes = { - editing: PropTypes.bool, - toggleEditing: PropTypes.func, + isWriteable: PropTypes.bool, + toggleWriteable: PropTypes.func, hasAssets: PropTypes.bool, addElement: PropTypes.func.isRequired, showElementModal: PropTypes.bool, diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/index.js b/x-pack/plugins/canvas/public/components/workpad_loader/index.js index 8eee65d70df02..6229fd340813a 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/index.js @@ -10,19 +10,29 @@ import { compose, withState, getContext, withHandlers } from 'recompose'; import fileSaver from 'file-saver'; import * as workpadService from '../../lib/workpad_service'; import { notify } from '../../lib/notify'; +import { canUserWrite } from '../../state/selectors/app'; import { getWorkpad } from '../../state/selectors/workpad'; import { getId } from '../../lib/get_id'; +import { setCanUserWrite } from '../../state/actions/transient'; import { WorkpadLoader as Component } from './workpad_loader'; const mapStateToProps = state => ({ workpadId: getWorkpad(state).id, + canUserWrite: canUserWrite(state), +}); + +const mapDispatchToProps = dispatch => ({ + setCanUserWrite: canUserWrite => dispatch(setCanUserWrite(canUserWrite)), }); export const WorkpadLoader = compose( getContext({ router: PropTypes.object, }), - connect(mapStateToProps), + connect( + mapStateToProps, + mapDispatchToProps + ), withState('workpads', 'setWorkpads', null), withHandlers({ // Workpad creation via navigation @@ -34,6 +44,9 @@ export const WorkpadLoader = compose( props.router.navigateTo('loadWorkpad', { id: workpad.id, page: 1 }); } catch (err) { notify.error(err, { title: `Couldn't upload workpad` }); + // TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced + // https://github.com/elastic/kibana/issues/20277 + if (err.response.status === 403) props.setCanUserWrite(false); } return; } @@ -72,6 +85,9 @@ export const WorkpadLoader = compose( props.router.navigateTo('loadWorkpad', { id: workpad.id, page: 1 }); } catch (err) { notify.error(err, { title: `Couldn't clone workpad` }); + // TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced + // https://github.com/elastic/kibana/issues/20277 + if (err.response.status === 403) props.setCanUserWrite(false); } }, @@ -96,8 +112,14 @@ export const WorkpadLoader = compose( ([passes, errors], result) => { if (result.id === loadedWorkpad && !result.err) redirectHome = true; - if (result.err) errors.push(result.id); - else passes.push(result.id); + if (result.err) { + errors.push(result.id); + // TODO: remove this and switch to checking user privileges when canvas loads when granular app privileges are introduced + // https://github.com/elastic/kibana/issues/20277 + if (result.err.response.status === 403) props.setCanUserWrite(false); + } else { + passes.push(result.id); + } return [passes, errors]; }, diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_create.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_create.js index 3caf4d553cca2..77f22a8252dab 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_create.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_create.js @@ -8,8 +8,15 @@ import React from 'react'; import PropTypes from 'prop-types'; import { EuiButton } from '@elastic/eui'; -export const WorkpadCreate = ({ createPending, onCreate }) => ( - +export const WorkpadCreate = ({ createPending, onCreate, ...rest }) => ( + Create workpad ); diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js index f2cce0118c6ed..396ded22f6d1f 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/index.js @@ -5,15 +5,14 @@ */ import PropTypes from 'prop-types'; -import { compose, withState, withHandlers } from 'recompose'; +import { compose, withHandlers } from 'recompose'; import { getId } from '../../../lib/get_id'; import { notify } from '../../../lib/notify'; import { WorkpadDropzone as Component } from './workpad_dropzone'; export const WorkpadDropzone = compose( - withState('isDropping', 'setDropping', false), withHandlers({ - onDropAccepted: ({ onUpload, setDropping }) => ([file]) => { + onDropAccepted: ({ onUpload }) => ([file]) => { // TODO: Clean up this file, this loading stuff can, and should be, abstracted const reader = new FileReader(); @@ -30,13 +29,11 @@ export const WorkpadDropzone = compose( // read the uploaded file reader.readAsText(file); - setDropping(false); }, - onDropRejected: ({ setDropping }) => ([file]) => { + onDropRejected: () => ([file]) => { notify.warning('Only JSON files are accepted', { title: `Couldn't upload '${file.name || 'file'}'`, }); - setDropping(false); }, }) )(Component); diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/workpad_dropzone.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/workpad_dropzone.js index 274c8ef2cc9b8..8a616964623db 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/workpad_dropzone.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_dropzone/workpad_dropzone.js @@ -8,14 +8,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import Dropzone from 'react-dropzone'; -export const WorkpadDropzone = ({ setDropping, onDropAccepted, onDropRejected, children }) => ( +export const WorkpadDropzone = ({ onDropAccepted, onDropRejected, disabled, children }) => ( setDropping(true)} - onDragLeave={() => setDropping(false)} disableClick + disabled={disabled} className="canvasWorkpad__dropzone" activeClassName="canvasWorkpad__dropzone--active" > @@ -24,9 +23,8 @@ export const WorkpadDropzone = ({ setDropping, onDropAccepted, onDropRejected, c ); WorkpadDropzone.propTypes = { - isDropping: PropTypes.bool.isRequired, - setDropping: PropTypes.func.isRequired, onDropAccepted: PropTypes.func.isRequired, onDropRejected: PropTypes.func.isRequired, + disabled: PropTypes.bool.isRequired, children: PropTypes.node.isRequired, }; diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js index badb52af77cd2..f56cc3a78f842 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js @@ -35,6 +35,7 @@ const formatDate = date => date && moment(date).format('MMM D, YYYY @ h:mma'); export class WorkpadLoader extends React.PureComponent { static propTypes = { workpadId: PropTypes.string.isRequired, + canUserWrite: PropTypes.bool.isRequired, createWorkpad: PropTypes.func.isRequired, findWorkpads: PropTypes.func.isRequired, downloadWorkpad: PropTypes.func.isRequired, @@ -133,6 +134,7 @@ export class WorkpadLoader extends React.PureComponent { renderWorkpadTable = ({ rows, pageNumber, totalPages, setPage }) => { const { sortField, sortDirection } = this.state; + const { canUserWrite, createPending } = this.props; const actions = [ { @@ -148,11 +150,14 @@ export class WorkpadLoader extends React.PureComponent { - + this.cloneWorkpad(workpad)} aria-label="Clone Workpad" + disabled={!canUserWrite} /> @@ -227,7 +232,7 @@ export class WorkpadLoader extends React.PureComponent { return ( - + + ); + + let deleteButton = ( + + {`Delete (${selectedWorkpads.length})`} + + ); + + const downloadButton = ( + + {`Download (${selectedWorkpads.length})`} + + ); + + let uploadButton = ( + + ); + + if (!canUserWrite) { + createButton = ( + + {createButton} + + ); + deleteButton = ( + + {deleteButton} + + ); + uploadButton = ( + + {uploadButton} + + ); + } + const modalTitle = selectedWorkpads.length === 1 ? `Delete workpad '${selectedWorkpads[0].name}'?` @@ -296,26 +351,8 @@ export class WorkpadLoader extends React.PureComponent { {selectedWorkpads.length > 0 && ( - - - {`Download (${selectedWorkpads.length})`} - - - - - {`Delete (${selectedWorkpads.length})`} - - + {downloadButton} + {deleteButton} )} @@ -330,18 +367,8 @@ export class WorkpadLoader extends React.PureComponent { - - - - - - + {uploadButton} + {createButton} diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_upload.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_upload.js index d2be98bada119..690f563676d8f 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_upload.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_upload.js @@ -11,8 +11,9 @@ import { get } from 'lodash'; import { getId } from '../../lib/get_id'; import { notify } from '../../lib/notify'; -export const WorkpadUpload = ({ onUpload }) => ( +export const WorkpadUpload = ({ onUpload, ...rest }) => ( { diff --git a/x-pack/plugins/canvas/public/components/workpad_page/index.js b/x-pack/plugins/canvas/public/components/workpad_page/index.js index bf3e220ddfbbb..6d7d13f155a7e 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/index.js @@ -9,14 +9,14 @@ import PropTypes from 'prop-types'; import { compose, withState, withProps } from 'recompose'; import { aeroelastic } from '../../lib/aeroelastic_kibana'; import { removeElements } from '../../state/actions/elements'; -import { getFullscreen, getEditing } from '../../state/selectors/app'; -import { getElements } from '../../state/selectors/workpad'; +import { getFullscreen, canUserWrite } from '../../state/selectors/app'; +import { getElements, isWriteable } from '../../state/selectors/workpad'; import { withEventHandlers } from './event_handlers'; import { WorkpadPage as Component } from './workpad_page'; const mapStateToProps = (state, ownProps) => { return { - isEditable: !getFullscreen(state) && getEditing(state), + isEditable: !getFullscreen(state) && isWriteable(state) && canUserWrite(state), elements: getElements(state, ownProps.page.id), }; }; diff --git a/x-pack/plugins/canvas/public/lib/__tests__/history_provider.js b/x-pack/plugins/canvas/public/lib/__tests__/history_provider.js index f24e27cc6d7f2..053123adeafcd 100644 --- a/x-pack/plugins/canvas/public/lib/__tests__/history_provider.js +++ b/x-pack/plugins/canvas/public/lib/__tests__/history_provider.js @@ -11,7 +11,6 @@ import { historyProvider } from '../history_provider'; function createState() { return { transient: { - editing: false, selectedPage: 'page-f3ce-4bb7-86c8-0417606d6592', selectedElement: 'element-d88c-4bbd-9453-db22e949b92e', resolvedArgs: {}, diff --git a/x-pack/plugins/canvas/public/state/actions/transient.js b/x-pack/plugins/canvas/public/state/actions/transient.js index 19d9a76b6a497..93464a23bf500 100644 --- a/x-pack/plugins/canvas/public/state/actions/transient.js +++ b/x-pack/plugins/canvas/public/state/actions/transient.js @@ -6,6 +6,6 @@ import { createAction } from 'redux-actions'; -export const setEditing = createAction('setEditing'); +export const setCanUserWrite = createAction('setCanUserWrite'); export const setFullscreen = createAction('setFullscreen'); export const selectElement = createAction('selectElement'); diff --git a/x-pack/plugins/canvas/public/state/actions/workpad.js b/x-pack/plugins/canvas/public/state/actions/workpad.js index 5f326176999ea..6e41ac36db19c 100644 --- a/x-pack/plugins/canvas/public/state/actions/workpad.js +++ b/x-pack/plugins/canvas/public/state/actions/workpad.js @@ -12,6 +12,7 @@ import { fetchAllRenderables } from './elements'; export const sizeWorkpad = createAction('sizeWorkpad'); export const setName = createAction('setName'); +export const setWriteable = createAction('setWriteable'); export const setColors = createAction('setColors'); export const setRefreshInterval = createAction('setRefreshInterval'); diff --git a/x-pack/plugins/canvas/public/state/defaults.js b/x-pack/plugins/canvas/public/state/defaults.js index 41f79ca60490c..d932bc80a93e0 100644 --- a/x-pack/plugins/canvas/public/state/defaults.js +++ b/x-pack/plugins/canvas/public/state/defaults.js @@ -77,5 +77,6 @@ export const getDefaultWorkpad = () => { '#FFFFFF', 'rgba(255,255,255,0)', // 'transparent' ], + isWriteable: true, }; }; diff --git a/x-pack/plugins/canvas/public/state/initial_state.js b/x-pack/plugins/canvas/public/state/initial_state.js index c9bf04185bd17..7d35c9a2bacf7 100644 --- a/x-pack/plugins/canvas/public/state/initial_state.js +++ b/x-pack/plugins/canvas/public/state/initial_state.js @@ -11,7 +11,7 @@ export const getInitialState = path => { const state = { app: {}, // Kibana stuff in here transient: { - editing: true, + canUserWrite: true, fullscreen: false, selectedElement: null, resolvedArgs: {}, diff --git a/x-pack/plugins/canvas/public/state/middleware/es_persist.js b/x-pack/plugins/canvas/public/state/middleware/es_persist.js index e36f5d3586f20..52e23a5d6195e 100644 --- a/x-pack/plugins/canvas/public/state/middleware/es_persist.js +++ b/x-pack/plugins/canvas/public/state/middleware/es_persist.js @@ -13,6 +13,7 @@ import * as transientActions from '../actions/transient'; import * as resolvedArgsActions from '../actions/resolved_args'; import { update } from '../../lib/workpad_service'; import { notify } from '../../lib/notify'; +import { canUserWrite } from '../selectors/app'; const workpadChanged = (before, after) => { const workpad = getWorkpad(before); @@ -43,6 +44,9 @@ export const esPersistMiddleware = ({ getState }) => { next(action); const newState = getState(); + // skips the update request if user doesn't have write permissions + if (!canUserWrite(newState)) return; + // if the workpad changed, save it to elasticsearch if (workpadChanged(curState, newState) || assetsChanged(curState, newState)) { const persistedWorkpad = getWorkpadPersisted(getState()); diff --git a/x-pack/plugins/canvas/public/state/reducers/transient.js b/x-pack/plugins/canvas/public/state/reducers/transient.js index 6c2983fd850e3..a99d85b399c7e 100644 --- a/x-pack/plugins/canvas/public/state/reducers/transient.js +++ b/x-pack/plugins/canvas/public/state/reducers/transient.js @@ -28,8 +28,8 @@ export const transientReducer = handleActions( ); }, - [actions.setEditing]: (transientState, { payload }) => { - return set(transientState, 'editing', Boolean(payload)); + [actions.setCanUserWrite]: (transientState, { payload }) => { + return set(transientState, 'canUserWrite', Boolean(payload)); }, [actions.setFullscreen]: (transientState, { payload }) => { diff --git a/x-pack/plugins/canvas/public/state/reducers/workpad.js b/x-pack/plugins/canvas/public/state/reducers/workpad.js index 799444864fa41..892c541e5f348 100644 --- a/x-pack/plugins/canvas/public/state/reducers/workpad.js +++ b/x-pack/plugins/canvas/public/state/reducers/workpad.js @@ -6,7 +6,7 @@ import { recentlyAccessed } from 'ui/persisted_log'; import { handleActions } from 'redux-actions'; -import { setWorkpad, sizeWorkpad, setColors, setName } from '../actions/workpad'; +import { setWorkpad, sizeWorkpad, setColors, setName, setWriteable } from '../actions/workpad'; import { APP_ROUTE_WORKPAD } from '../../../common/lib/constants'; export const workpadReducer = handleActions( @@ -28,6 +28,10 @@ export const workpadReducer = handleActions( recentlyAccessed.add(`${APP_ROUTE_WORKPAD}/${workpadState.id}`, payload, workpadState.id); return { ...workpadState, name: payload }; }, + + [setWriteable]: (workpadState, { payload }) => { + return { ...workpadState, isWriteable: Boolean(payload) }; + }, }, {} ); diff --git a/x-pack/plugins/canvas/public/state/selectors/__tests__/workpad.js b/x-pack/plugins/canvas/public/state/selectors/__tests__/workpad.js index 0c459fc3faa84..f1ff31874935b 100644 --- a/x-pack/plugins/canvas/public/state/selectors/__tests__/workpad.js +++ b/x-pack/plugins/canvas/public/state/selectors/__tests__/workpad.js @@ -71,6 +71,7 @@ describe('workpad selectors', () => { ], }, ], + isWriteable: false, }, }, }; @@ -85,6 +86,7 @@ describe('workpad selectors', () => { expect(selector.getElementById({}, 'element-1')).to.be(undefined); expect(selector.getResolvedArgs({}, 'element-1')).to.be(undefined); expect(selector.getSelectedResolvedArgs({})).to.be(undefined); + expect(selector.isWriteable({})).to.be(true); }); }); @@ -195,4 +197,10 @@ describe('workpad selectors', () => { expect(arg).to.be(true); }); }); + + describe('isWriteable', () => { + it('returns boolean for if the workpad is writeable', () => { + expect(selector.isWriteable(state)).to.equal(false); + }); + }); }); diff --git a/x-pack/plugins/canvas/public/state/selectors/app.js b/x-pack/plugins/canvas/public/state/selectors/app.js index 3114cb2063440..b33dce4a86b42 100644 --- a/x-pack/plugins/canvas/public/state/selectors/app.js +++ b/x-pack/plugins/canvas/public/state/selectors/app.js @@ -7,8 +7,8 @@ import { get } from 'lodash'; // page getters -export function getEditing(state) { - return get(state, 'transient.editing', false); +export function canUserWrite(state) { + return get(state, 'transient.canUserWrite', true); } export function getFullscreen(state) { diff --git a/x-pack/plugins/canvas/public/state/selectors/workpad.js b/x-pack/plugins/canvas/public/state/selectors/workpad.js index 4dff29be51c01..1db0128abab07 100644 --- a/x-pack/plugins/canvas/public/state/selectors/workpad.js +++ b/x-pack/plugins/canvas/public/state/selectors/workpad.js @@ -27,10 +27,15 @@ export function getWorkpadPersisted(state) { assets: getAssets(state), }; } + export function getWorkpadInfo(state) { return omit(getWorkpad(state), ['pages']); } +export function isWriteable(state) { + return get(state, append(workpadRoot, 'isWriteable'), true); +} + // page getters export function getSelectedPageIndex(state) { return get(state, append(workpadRoot, 'page'));