Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read Only Mode in Canvas #23304

Merged
merged 27 commits into from
Oct 22, 2018
Merged

Read Only Mode in Canvas #23304

merged 27 commits into from
Oct 22, 2018

Conversation

cqliu1
Copy link
Contributor

@cqliu1 cqliu1 commented Sep 19, 2018

Closes #22979.
Closes #23126.

This implements a read only mode in Canvas for users with only read permissions.

Changes

  • adds canUserWrite to transient state
  • sets canUserWrite to false when user doesn't have write permissions
  • adds isWriteable to workpad object
  • shows bottom toolbar when editing is disabled
  • hides page controls and page add button in page manager when editing is disabled
  • disables drag and drop for reordering pages when editing is disabled
  • hides the edit mode toggle if user does not have write permissions

Update 9/26/2018

  • renames/moves editing in transient state to readOnly to persistent workpad state. Now, you can export read only workpads and they will upload with edit mode disabled. Once uploaded and if the user has write permissions, the user can turn on edit mode.
  • omits workpad create/upload/clone/delete controls in workpad loader for users with read only permissions

Update 10/4/18

  • switches the eye icon to a lock icon for turning edit mode on/off. Thanks @ryankeairns for the new icon!
    oct-04-2018 15-23-03

@cqliu1 cqliu1 added review Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas labels Sep 19, 2018
@@ -43,6 +44,8 @@ export const esPersistMiddleware = ({ getState }) => {
next(action);
const newState = getState();

if (getReadOnly(newState)) return;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rashidkpc I tried moving the current page from being stored as part of the workpad to transient state, but there were conflicts with the aeroelastic store where it relies the selected page to stay in the workpad state. I talked to @w33ble about the issues I was encountering, and we decided moving the selected page to transient state may be easier to implement after refactoring our redux implementation.

This was the simpler fix for preventing the user from updating the current page in the saved workpad object in elasticsearch.

@cqliu1
Copy link
Contributor Author

cqliu1 commented Sep 19, 2018

@alexfrancoeur @rashidkpc With the changes in this PR, it only sets Canvas in read only mode when the user doesn't have write permissions. Should I also provide an option somewhere to export the workpad as read only and read it off of the workpad JSON on upload? Maybe add a lock button in the workpad header to set the workpad to read only.

@elasticmachine
Copy link
Contributor

💔 Build Failed

@alexfrancoeur
Copy link

@cqliu1 I like the idea of exporting in read only. That would allow us to add demos to demo.elastic.co and then users could download them as well. A lock button would be nice too. I'll try and take this for a spin today.

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@alexfrancoeur
Copy link

Opening an existing workpad now works like a charm. I'm using the "old" way to create a read only role. We may want to check with @kobelb and @legrego to see if the read only roles is significantly different with 6.5

screen shot 2018-09-21 at 8 43 04 am

As a read only user, I can still "create a workpad", just with constant errors. It makes for an awkward experience because every time you move an element you get a flurry of errors. Maybe we should disable the button?

screen shot 2018-09-21 at 8 41 56 am

@asawariS @gingerwizard if we provide a read only mode in this fashion, end users will not be able to create and/or manipulate workpads on demo.elastic.co. I think this could be fine. We're showing the art of the possible with example workpads but if they want to get a feel for how to build them, they should spin up a trial. Because of the way Canvas saves progress, the only other way I can think of providing the "build" experience without saving is to simply hide or ignore the errors with a flag or something.

@legrego
Copy link
Member

legrego commented Sep 21, 2018

We may want to check with @kobelb and @legrego to see if the read only roles is significantly different with 6.5

6.5 likely won't help here, at least not in its current state. 6.6/Granular app privs will provide the necessary hooks for all applications to make decisions based on the current user's capabilities, so if you can wait for 6.6, that'd be preferred.

There is an outside chance that we could provide a flag to the UI in 6.5 indicating if the user is a readonly user, but it would be a temporary measure, as 6.6/Granular App Privileges will fundamentally change the way this works.

I'm saying "outside chance" because we'll want to wait for Spaces to merge into master before evaluating the feasibility. Then time permitting, we can open a PR to provide this to Canvas. This flag would be available within x-pack, so it'd work as long as you don't need it in OSS

@alexfrancoeur
Copy link

Thanks @legrego, just trying to understand if the read only role with RBAC phase 1/2 changes how we plan on handling it. If it's the same as earlier, I think we'll be fine. @cqliu1 seems to be detecting a read only state already.

@kobelb
Copy link
Contributor

kobelb commented Sep 21, 2018

From my understanding, we're trying to update the workpad with the existing workpad and if we get a 403, then we assume we're in read-only mode per:

              workpadService.update(params.id, fetchedWorkpad).catch(err => {
                if (err.response.status === 403) {
                  dispatch(setReadOnly(true));
                  dispatch(setEditing(false));
                }
              });

It's an interesting approach, that could potentially cause someone's changes to be overwritten if there's a certain timing of events, but from the security perspective it should work with RBAC and the legacy fallback to determine whether the user can edit workpads.

@rashidkpc rashidkpc changed the title Feat: Read Only Mode in Canvas [WIP] Read Only Mode in Canvas Sep 26, 2018
@rashidkpc rashidkpc removed their request for review September 26, 2018 19:11
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),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isDropping is unused as far as I can tell, so I just removed it.

@alexfrancoeur
Copy link

@legrego is there no easy way to check a users permissions upon entering the app? Something like https://www.elastic.co/guide/en/kibana/6.x/role-management-api-get.html but for users? I'm assuming not given your previous statement.

I wonder if we can gain any insight here into how dashboard only mode was introduced? I'm guessing that the app itself checks for the dashboard_only_role or any others from the advanced setting list. Which is a completely different approach but I wonder if we can get creative here.

@legrego
Copy link
Member

legrego commented Oct 15, 2018

@alexfrancoeur there's no great way to check a user's permissions when entering the app. The authorization logic is rather complex because of all of the permutations we need to allow:

  1. Security + Spaces with the new privilege system
  2. Security + Spaces with the legacy privilege system
  3. Security w/o Spaces with the new privilege system
  4. Security w/o Spaces with the legacy privilege system
  5. Spaces w/o Security
  6. All of the above with and w/o Canvas enabled

The implementation we have for 6.5 is very focused, and unfortunately not extensible for the canvas use case.

Dashboard only mode was also a very focused implementation, and from what I can tell, it's method isn't something we can apply here.

Copy link

@alexfrancoeur alexfrancoeur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke with Catherine about this. We cannot avoid the "flash" of the editing controls upon first interaction within the Canvas app until we have granular application permissions in Kibana. @legrego & @kobelb are targeting this for 6.6/6.x. I'd like to have the logic using granular app permissions before GA if possible. While this experience isn't ideal, I feel that it's fine for beta. Once merged, I'll open an issue to track adding granular app permissions so we can know the users roles & permissions for an application when they enter it. This will allow us to control the experience appropriately.

That being said, LGTM.

@rashidkpc rashidkpc requested review from clintandrewhall and removed request for rashidkpc October 16, 2018 18:35
@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

Copy link
Contributor

@clintandrewhall clintandrewhall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spoke with @cqliu1 yesterday, and I'm suggesting we divide this read-only change into two concepts:

1/ The viewer does not have permission within Kibana/Canvas to write workpads, and
2/ A particular workpad is not writable by the viewer, (for whatever reason, though there is only one today).

So I'm recommending we take this opportunity to augment the user model to include permissions information around Canvas while at the same time dividing that concern from the permissions of an individual workpad.

I think I caught all of the places below, but YMMV...!

@@ -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 { setReadOnlyUser } from '../../state/actions/transient';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider setUserCanRead, (which would default to true) and setUserCanWrite, which would be set based on the success or error response.

Suggested change
import { setReadOnlyUser } from '../../state/actions/transient';
import { setUserCanRead, setUserCanWrite } from '../../state/actions/transient';

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently there are only read or all privileges in Kibana, so I don't believe it's necessary to track read permissions since every user at a baseline has read privileges. It should be sufficient to just track if the user has write permissions.

@@ -29,6 +30,8 @@ export const routes = [
router.redirectTo('loadWorkpad', { id: newWorkpad.id, page: 1 });
} catch (err) {
notify.error(err, { title: `Couldn't create workpad` });
if (err.response.status === 403) dispatch(setReadOnlyUser(true));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (err.response.status === 403) dispatch(setReadOnlyUser(true));
if (err.response.status === 403) dispatch(setUserCanWrite(false));


// tests if user has permissions to write to workpads
workpadService.update(params.id, fetchedWorkpad).catch(err => {
if (err.response.status === 403) dispatch(setReadOnlyUser(true));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (err.response.status === 403) dispatch(setReadOnlyUser(true));
if (err.response.status === 403) dispatch(setUserCanWrite(false));

@@ -48,6 +51,11 @@ export const routes = [
const { assets, ...workpad } = fetchedWorkpad;
dispatch(setWorkpad(workpad));
dispatch(setAssets(assets));

// tests if user has permissions to write to workpads
workpadService.update(params.id, fetchedWorkpad).catch(err => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you mentioned there will be a new permissions API forthcoming, consider adding a TODO comment here with a follow-up task?

@@ -8,16 +8,16 @@ 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 { getReadOnlyUser, getAppReady } from '../../../state/selectors/app';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider boolean-style getters.

Suggested change
import { getReadOnlyUser, getAppReady } from '../../../state/selectors/app';
import { canUserRead, canUserWrite, getAppReady } from '../../../state/selectors/app';

@@ -11,7 +11,7 @@ export const getInitialState = path => {
const state = {
app: {}, // Kibana stuff in here
transient: {
editing: true,
readOnlyUser: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
readOnlyUser: false,
canUserRead: true,
canUserWrite: true,

@@ -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 { getReadOnlyUser } from '../selectors/app';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { getReadOnlyUser } from '../selectors/app';
import { canUserWrite } from '../selectors/app';

@@ -43,6 +44,9 @@ export const esPersistMiddleware = ({ getState }) => {
next(action);
const newState = getState();

// skips the update request if in read only mode
if (getReadOnlyUser(newState)) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (getReadOnlyUser(newState)) return;
if (!canUserWrite(newState)) return;

@@ -28,8 +28,8 @@ export const transientReducer = handleActions(
);
},

[actions.setEditing]: (transientState, { payload }) => {
return set(transientState, 'editing', Boolean(payload));
[actions.setReadOnlyUser]: (transientState, { payload }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[actions.setReadOnlyUser]: (transientState, { payload }) => {
[actions.setCanUserWrite]: (transientState, { payload }) => {

[actions.setEditing]: (transientState, { payload }) => {
return set(transientState, 'editing', Boolean(payload));
[actions.setReadOnlyUser]: (transientState, { payload }) => {
return set(transientState, 'readOnlyUser', Boolean(payload));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return set(transientState, 'readOnlyUser', Boolean(payload));
return set(transientState, 'canUserWrite', Boolean(payload));

Copy link
Contributor

@clintandrewhall clintandrewhall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spoke with @cqliu1 yesterday, and I'm suggesting we divide this read-only change into two concepts:

1/ The viewer does not have permission within Kibana/Canvas to write workpads, and
2/ A particular workpad is not writable by the viewer, (for whatever reason, though there is only one today).

So I'm recommending we take this opportunity to augment the user model to include permissions information around Canvas while at the same time dividing that concern from the permissions of an individual workpad.

I think I caught all of the places below, but YMMV...!

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@@ -25,7 +25,6 @@ const mapStateToProps = state => ({
totalElementCount: getAllElements(state).length,
workpad: getWorkpad(state),
isFullscreen: getFullscreen(state),
isEditing: getEditing(state),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prop wasn't being used in the workpad component, so I removed it.

@cqliu1
Copy link
Contributor Author

cqliu1 commented Oct 18, 2018

@clintandrewhall This is ready for another look when you get a chance. I made all of the changes you requested and added a couple tests for the isWriteable workpad selector.

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@cqliu1 cqliu1 merged commit 9d424f9 into elastic:master Oct 22, 2018
Canvas (6.6 @ 12/18, 6.7 @ 01/29) automation moved this from In Progress to Done Oct 22, 2018
@cqliu1 cqliu1 deleted the feat/read-only-mode branch October 22, 2018 21:01
cqliu1 added a commit to cqliu1/kibana that referenced this pull request Oct 22, 2018
* Added readOnly to transient state

* Added check that user has permission to edit when loading workpads

* Shows toolbar in readOnly mode

* Disables page controls in read only mode

* Disables add page button when editing disabled

* Added comment to workpad routes

* Cleaned up page manager styles

* Added a comment to es_persist

* Renamed transient.readOnly to transient.readOnlyUser

* Replaced isEditing in transient state with readOnly in workpad state

* Cleaned up workpad_header

* Fixed logic in workpad_header

* Disables workpad controls instead of hiding them and adds tooltips

* Switched eye icon to lock icon in workpad header

* Renamed readOnly and readOnlyUser variables and functions

* Added dispatch to update canUserWrite in create/clone/delete workpads

* Fixed typo

* Removed unnecessary prop from workpad

* Refactored tooltips in workpad_loader

* Added workpad selector tests
cqliu1 added a commit that referenced this pull request Oct 23, 2018
* Added readOnly to transient state

* Added check that user has permission to edit when loading workpads

* Shows toolbar in readOnly mode

* Disables page controls in read only mode

* Disables add page button when editing disabled

* Added comment to workpad routes

* Cleaned up page manager styles

* Added a comment to es_persist

* Renamed transient.readOnly to transient.readOnlyUser

* Replaced isEditing in transient state with readOnly in workpad state

* Cleaned up workpad_header

* Fixed logic in workpad_header

* Disables workpad controls instead of hiding them and adds tooltips

* Switched eye icon to lock icon in workpad header

* Renamed readOnly and readOnlyUser variables and functions

* Added dispatch to update canUserWrite in create/clone/delete workpads

* Fixed typo

* Removed unnecessary prop from workpad

* Refactored tooltips in workpad_loader

* Added workpad selector tests
njd5475 pushed a commit to njd5475/kibana that referenced this pull request Oct 24, 2018
* Added readOnly to transient state

* Added check that user has permission to edit when loading workpads

* Shows toolbar in readOnly mode

* Disables page controls in read only mode

* Disables add page button when editing disabled

* Added comment to workpad routes

* Cleaned up page manager styles

* Added a comment to es_persist

* Renamed transient.readOnly to transient.readOnlyUser

* Replaced isEditing in transient state with readOnly in workpad state

* Cleaned up workpad_header

* Fixed logic in workpad_header

* Disables workpad controls instead of hiding them and adds tooltips

* Switched eye icon to lock icon in workpad header

* Renamed readOnly and readOnlyUser variables and functions

* Added dispatch to update canUserWrite in create/clone/delete workpads

* Fixed typo

* Removed unnecessary prop from workpad

* Refactored tooltips in workpad_loader

* Added workpad selector tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
review Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas v6.5.0 v7.0.0
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

None yet

6 participants