@@ -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'));