From 80924a975f62826777510bafb78110de40f82e91 Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Fri, 18 Oct 2019 19:31:43 -0300 Subject: [PATCH 1/8] Create PermissionsEditor based on SelectItems --- client/app/components/SelectItemsDialog.jsx | 9 ++- .../PermissionsEditorDialog.jsx | 67 +++++++++++++++++++ client/app/pages/queries/view.js | 21 ++++-- 3 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 client/app/components/permissions-editor/PermissionsEditorDialog.jsx diff --git a/client/app/components/SelectItemsDialog.jsx b/client/app/components/SelectItemsDialog.jsx index f54dc27773..ace2ef379e 100644 --- a/client/app/components/SelectItemsDialog.jsx +++ b/client/app/components/SelectItemsDialog.jsx @@ -29,6 +29,7 @@ class SelectItemsDialog extends React.Component { renderItem: PropTypes.func, // right list; args/results save as for `renderItem`. if not specified - `renderItem` will be used renderStagedItem: PropTypes.func, + defaultSelectedItem: PropTypes.func, // (item) => bool save: PropTypes.func, // (selectedItems[]) => Promise width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), extraFooterContent: PropTypes.node, @@ -42,6 +43,7 @@ class SelectItemsDialog extends React.Component { itemKey: item => item.id, renderItem: () => '', renderStagedItem: null, // hidden by default + defaultSelectedItem: () => false, // no items are selected by default save: items => items, width: '80%', extraFooterContent: null, @@ -57,7 +59,7 @@ class SelectItemsDialog extends React.Component { }; // eslint-disable-next-line react/sort-comp - loadItems = (searchTerm = '') => { + loadItems = (searchTerm = '', assignSelected = false) => { this.setState({ searchTerm, loading: true }, () => { this.props.searchItems(searchTerm) .then((items) => { @@ -65,6 +67,9 @@ class SelectItemsDialog extends React.Component { if (this.state.searchTerm === searchTerm) { this.setState({ items, loading: false }); } + if (assignSelected) { + this.setState({ selected: filter(items, this.props.defaultSelectedItem) }); + } }) .catch(() => { if (this.state.searchTerm === searchTerm) { @@ -77,7 +82,7 @@ class SelectItemsDialog extends React.Component { search = debounce(this.loadItems, 200); componentDidMount() { - this.loadItems(); + this.loadItems('', true); } isSelected(item) { diff --git a/client/app/components/permissions-editor/PermissionsEditorDialog.jsx b/client/app/components/permissions-editor/PermissionsEditorDialog.jsx new file mode 100644 index 0000000000..1e638c5a20 --- /dev/null +++ b/client/app/components/permissions-editor/PermissionsEditorDialog.jsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { each, map, includes, find, get, difference } from 'lodash'; +import Tag from 'antd/lib/tag'; +import SelectItemsDialog from '@/components/SelectItemsDialog'; +import { UserPreviewCard } from '@/components/PreviewCard'; +import { User } from '@/services/user'; +import { $http } from '@/services/ng'; +import { toHuman } from '@/filters'; + +const loadGrantees = aclUrl => $http.get(aclUrl).then(({ data }) => { + const resultGrantees = []; + each(data, (grantees, accessType) => { + grantees.forEach((grantee) => { + grantee.accessType = toHuman(accessType); + resultGrantees.push(grantee); + }); + }); + return resultGrantees; +}); + +function showModal({ aclUrl, ownerId }) { + return loadGrantees(aclUrl).then((grantees) => { + const usersWithPermissions = [ownerId, ...map(grantees, grantee => grantee.id)]; + return SelectItemsDialog.showModal({ + dialogTitle: 'Manage Permissions', + inputPlaceholder: 'Search users...', + selectedItemsTitle: 'Users with permissions', + searchItems: searchTerm => User.query({ q: searchTerm }).$promise.then(({ results }) => results), + renderItem: (item, { isSelected }) => ({ + content: ( + + {includes(usersWithPermissions, item.id) && (isSelected ? ( + {get(find(grantees, { id: item.id }), 'accessType', 'Owner')} + ) : Not Saved)} + {item.id !== ownerId && } + + ), + className: isSelected ? 'selected' : '', + isDisabled: isSelected && includes(usersWithPermissions, item.id), + }), + renderStagedItem: item => ({ + content: ( + + {includes(usersWithPermissions, item.id) ? ( + {get(find(grantees, { id: item.id }), 'accessType', 'Owner')} + ) : Not Saved} + {item.id !== ownerId && } + + ), + isDisabled: item.id === ownerId, + }), + defaultSelectedItem: item => includes(usersWithPermissions, item.id), + }).result + .then((selected) => { + const selectedIds = map(selected, s => s.id); + return { + added: difference(selectedIds, usersWithPermissions), + removed: difference(usersWithPermissions, selectedIds), + }; + }); + }); +} + +export default { + ...SelectItemsDialog, + showModal, +}; diff --git a/client/app/pages/queries/view.js b/client/app/pages/queries/view.js index 39934c166c..eed4323b4d 100644 --- a/client/app/pages/queries/view.js +++ b/client/app/pages/queries/view.js @@ -8,8 +8,10 @@ import ScheduleDialog from '@/components/queries/ScheduleDialog'; import { newVisualization } from '@/visualizations'; import EditVisualizationDialog from '@/visualizations/EditVisualizationDialog'; import EmbedQueryDialog from '@/components/queries/EmbedQueryDialog'; +import PermissionsEditorDialog from '@/components/permissions-editor/PermissionsEditorDialog'; import notification from '@/services/notification'; import template from './query.html'; +import { $http } from '@/services/ng'; function QueryViewCtrl( $scope, @@ -528,12 +530,19 @@ function QueryViewCtrl( ); $scope.showManagePermissionsModal = () => { - $uibModal.open({ - component: 'permissionsEditor', - resolve: { - aclUrl: { url: `api/queries/${$routeParams.queryId}/acl` }, - owner: $scope.query.user, - }, + const aclUrl = `api/queries/${$routeParams.queryId}/acl`; + PermissionsEditorDialog.showModal({ + aclUrl, + ownerId: $scope.query.user.id, + }).then(({ added, removed }) => { + if (!isEmpty(added) || !isEmpty(removed)) { + const addedPromises = map(added, userId => $http.post(aclUrl, { access_type: 'modify', user_id: userId })); + const removedPromises = map(removed, + userId => $http.delete(aclUrl, { data: { access_type: 'modify', user_id: userId } })); + Promise.all([...addedPromises, ...removedPromises]) + .then(() => notification.success('Permissions updated!')) + .catch(() => notification.error('Could not save one or more users')); + } }); }; } From 86f8d8b1759855540e88b1a7a9804a0c4da9df58 Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Tue, 22 Oct 2019 20:04:05 -0300 Subject: [PATCH 2/8] Create new dialog for PermissionsEditor --- client/app/components/HelpTrigger.jsx | 4 + .../PermissionsEditorDialog.jsx | 232 +++++++++++++----- .../PermissionsEditorDialog.less | 8 + client/app/pages/queries/view.js | 13 +- 4 files changed, 188 insertions(+), 69 deletions(-) create mode 100644 client/app/components/permissions-editor/PermissionsEditorDialog.less diff --git a/client/app/components/HelpTrigger.jsx b/client/app/components/HelpTrigger.jsx index 75b2a6f8dd..68fc112abd 100644 --- a/client/app/components/HelpTrigger.jsx +++ b/client/app/components/HelpTrigger.jsx @@ -83,6 +83,10 @@ export const TYPES = { '/user-guide/querying/favorites-tagging/#Favorites', 'Guide: Favorites', ], + MANAGE_PERMISSIONS: [ + '/user-guide/querying/writing-queries#Managing-Query-Permissions', + 'Guide: Managing Query Permissions', + ], }; export default class HelpTrigger extends React.Component { diff --git a/client/app/components/permissions-editor/PermissionsEditorDialog.jsx b/client/app/components/permissions-editor/PermissionsEditorDialog.jsx index 1e638c5a20..9a7105ca05 100644 --- a/client/app/components/permissions-editor/PermissionsEditorDialog.jsx +++ b/client/app/components/permissions-editor/PermissionsEditorDialog.jsx @@ -1,67 +1,183 @@ -import React from 'react'; -import { each, map, includes, find, get, difference } from 'lodash'; +import React, { useState, useEffect, useCallback } from 'react'; +import PropTypes from 'prop-types'; +import { each, debounce, get, find } from 'lodash'; +import Button from 'antd/lib/button'; +import List from 'antd/lib/list'; +import Modal from 'antd/lib/modal'; +import Select from 'antd/lib/select'; import Tag from 'antd/lib/tag'; -import SelectItemsDialog from '@/components/SelectItemsDialog'; -import { UserPreviewCard } from '@/components/PreviewCard'; -import { User } from '@/services/user'; +import Tooltip from 'antd/lib/tooltip'; +import { wrap as wrapDialog, DialogPropType } from '@/components/DialogWrapper'; +import LoadingState from '@/components/items-list/components/LoadingState'; import { $http } from '@/services/ng'; import { toHuman } from '@/filters'; +import HelpTrigger from '@/components/HelpTrigger'; +import { UserPreviewCard } from '@/components/PreviewCard'; +import notification from '@/services/notification'; +import { User } from '@/services/user'; -const loadGrantees = aclUrl => $http.get(aclUrl).then(({ data }) => { - const resultGrantees = []; - each(data, (grantees, accessType) => { - grantees.forEach((grantee) => { - grantee.accessType = toHuman(accessType); - resultGrantees.push(grantee); +import './PermissionsEditorDialog.less'; + +const { Option } = Select; +const DEBOUNCE_SEARCH_DURATION = 200; + +function useGrantees(url) { + const loadGrantees = useCallback(() => $http.get(url).then(({ data }) => { + const resultGrantees = []; + each(data, (grantees, accessType) => { + grantees.forEach((grantee) => { + grantee.accessType = toHuman(accessType); + resultGrantees.push(grantee); + }); }); - }); - return resultGrantees; -}); - -function showModal({ aclUrl, ownerId }) { - return loadGrantees(aclUrl).then((grantees) => { - const usersWithPermissions = [ownerId, ...map(grantees, grantee => grantee.id)]; - return SelectItemsDialog.showModal({ - dialogTitle: 'Manage Permissions', - inputPlaceholder: 'Search users...', - selectedItemsTitle: 'Users with permissions', - searchItems: searchTerm => User.query({ q: searchTerm }).$promise.then(({ results }) => results), - renderItem: (item, { isSelected }) => ({ - content: ( - - {includes(usersWithPermissions, item.id) && (isSelected ? ( - {get(find(grantees, { id: item.id }), 'accessType', 'Owner')} - ) : Not Saved)} - {item.id !== ownerId && } - - ), - className: isSelected ? 'selected' : '', - isDisabled: isSelected && includes(usersWithPermissions, item.id), - }), - renderStagedItem: item => ({ - content: ( - - {includes(usersWithPermissions, item.id) ? ( - {get(find(grantees, { id: item.id }), 'accessType', 'Owner')} - ) : Not Saved} - {item.id !== ownerId && } + return resultGrantees; + }), [url]); + + const addPermission = useCallback((userId, accessType = 'modify') => $http.post( + url, { access_type: accessType, user_id: userId }, + ).catch(() => notification.error('Could not grant permission to the user'), [url])); + + const removePermission = useCallback((userId, accessType = 'modify') => $http.delete( + url, { data: { access_type: accessType, user_id: userId } }, + ).catch(() => notification.error('Could not remove permission from the user')), [url]); + + return { loadGrantees, addPermission, removePermission }; +} + +const searchUsers = searchTerm => User.query({ q: searchTerm }).$promise + .then(({ results }) => results) + .catch(() => []); + +function PermissionsEditorDialogHeader({ context }) { + return ( + <> + Manage Permissions +
+ {`Editing this ${context} is enabled for the users in this list and for admins. `} + +
+ + ); +} + +PermissionsEditorDialogHeader.propTypes = { context: PropTypes.oneOf(['query', 'dashboard']) }; +PermissionsEditorDialogHeader.defaultProps = { context: 'query' }; + +function UserSelect({ onSelect, previewCardAddon, isUserDisabled }) { + const [loadingUsers, setLoadingUsers] = useState(true); + const [users, setUsers] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); + + const debouncedSearchUsers = useCallback(debounce( + search => searchUsers(search) + .then(setUsers) + .finally(() => setLoadingUsers(false)), + DEBOUNCE_SEARCH_DURATION, + ), []); + + useEffect(() => { + setLoadingUsers(true); + debouncedSearchUsers(searchTerm); + }, [searchTerm]); + + return ( + + ); } -export default { - ...SelectItemsDialog, - showModal, +UserSelect.propTypes = { + onSelect: PropTypes.func, + previewCardAddon: PropTypes.func, + isUserDisabled: PropTypes.func, }; +UserSelect.defaultProps = { onSelect: () => {}, previewCardAddon: () => null, isUserDisabled: () => false }; + +function PermissionsEditorDialog({ dialog, owner, context, aclUrl }) { + const [loadingGrantees, setLoadingGrantees] = useState(true); + const [grantees, setGrantees] = useState([]); + const { loadGrantees, addPermission, removePermission } = useGrantees(aclUrl); + const loadUsersWithPermissions = useCallback(() => { + setLoadingGrantees(true); + loadGrantees() + .then(setGrantees) + .catch(() => notification.error('Failed to load grantees list')) + .finally(() => setLoadingGrantees(false)); + }, []); + + const userAccessType = useCallback( + user => (user.id === owner.id ? 'Owner' : get(find(grantees, { id: user.id }), 'accessType')), + [grantees], + ); + + useEffect(() => { + loadUsersWithPermissions(); + }, [aclUrl]); + + return ( + } + footer={()} + > + addPermission(userId).then(loadUsersWithPermissions)} + previewCardAddon={userAccessType} + isUserDisabled={user => !!userAccessType(user)} + /> +
Users with permissions
+ {!loadingGrantees ? ( +
+ ( + + + {user.id === owner.id ? (Owner) : ( + + removePermission(user.id).then(loadUsersWithPermissions)} + /> + + )} + + + )} + /> +
+ ) : } +
+ ); +} + +PermissionsEditorDialog.propTypes = { + dialog: DialogPropType.isRequired, + owner: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + context: PropTypes.oneOf(['query', 'dashboard']), + aclUrl: PropTypes.string.isRequired, +}; + +PermissionsEditorDialog.defaultProps = { context: 'query' }; + +export default wrapDialog(PermissionsEditorDialog); diff --git a/client/app/components/permissions-editor/PermissionsEditorDialog.less b/client/app/components/permissions-editor/PermissionsEditorDialog.less new file mode 100644 index 0000000000..f89212de14 --- /dev/null +++ b/client/app/components/permissions-editor/PermissionsEditorDialog.less @@ -0,0 +1,8 @@ +.permissions-editor-dialog { + .ant-select-dropdown-menu-item-disabled { + // make sure .text-muted has the disabled color + &, .text-muted { + color: rgba(0, 0, 0, 0.25); + } + } +} \ No newline at end of file diff --git a/client/app/pages/queries/view.js b/client/app/pages/queries/view.js index eed4323b4d..d24ee6c306 100644 --- a/client/app/pages/queries/view.js +++ b/client/app/pages/queries/view.js @@ -11,7 +11,6 @@ import EmbedQueryDialog from '@/components/queries/EmbedQueryDialog'; import PermissionsEditorDialog from '@/components/permissions-editor/PermissionsEditorDialog'; import notification from '@/services/notification'; import template from './query.html'; -import { $http } from '@/services/ng'; function QueryViewCtrl( $scope, @@ -533,16 +532,8 @@ function QueryViewCtrl( const aclUrl = `api/queries/${$routeParams.queryId}/acl`; PermissionsEditorDialog.showModal({ aclUrl, - ownerId: $scope.query.user.id, - }).then(({ added, removed }) => { - if (!isEmpty(added) || !isEmpty(removed)) { - const addedPromises = map(added, userId => $http.post(aclUrl, { access_type: 'modify', user_id: userId })); - const removedPromises = map(removed, - userId => $http.delete(aclUrl, { data: { access_type: 'modify', user_id: userId } })); - Promise.all([...addedPromises, ...removedPromises]) - .then(() => notification.success('Permissions updated!')) - .catch(() => notification.error('Could not save one or more users')); - } + context: 'query', + owner: $scope.query.user, }); }; } From a7b7a60041073c47ed713b80dee7cff71ab649db Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Sat, 26 Oct 2019 14:16:46 -0300 Subject: [PATCH 3/8] Cleanup + Dashboard dialog --- .../PermissionsEditorDialog.jsx | 8 +- .../components/permissions-editor/index.js | 96 ------------------- .../permissions-editor.html | 47 --------- client/app/pages/dashboards/dashboard.js | 12 +-- 4 files changed, 10 insertions(+), 153 deletions(-) delete mode 100644 client/app/components/permissions-editor/index.js delete mode 100644 client/app/components/permissions-editor/permissions-editor.html diff --git a/client/app/components/permissions-editor/PermissionsEditorDialog.jsx b/client/app/components/permissions-editor/PermissionsEditorDialog.jsx index 9a7105ca05..46b91461a5 100644 --- a/client/app/components/permissions-editor/PermissionsEditorDialog.jsx +++ b/client/app/components/permissions-editor/PermissionsEditorDialog.jsx @@ -123,8 +123,8 @@ function PermissionsEditorDialog({ dialog, owner, context, aclUrl }) { .finally(() => setLoadingGrantees(false)); }, []); - const userAccessType = useCallback( - user => (user.id === owner.id ? 'Owner' : get(find(grantees, { id: user.id }), 'accessType')), + const userHasPermission = useCallback( + user => (user.id === owner.id || !!get(find(grantees, { id: user.id }), 'accessType')), [grantees], ); @@ -141,8 +141,8 @@ function PermissionsEditorDialog({ dialog, owner, context, aclUrl }) { > addPermission(userId).then(loadUsersWithPermissions)} - previewCardAddon={userAccessType} - isUserDisabled={user => !!userAccessType(user)} + previewCardAddon={user => (userHasPermission(user) ? '(already has permission)' : null)} + isUserDisabled={user => userHasPermission(user)} />
Users with permissions
{!loadingGrantees ? ( diff --git a/client/app/components/permissions-editor/index.js b/client/app/components/permissions-editor/index.js deleted file mode 100644 index d0a8a7631c..0000000000 --- a/client/app/components/permissions-editor/index.js +++ /dev/null @@ -1,96 +0,0 @@ -import { includes, each, filter } from 'lodash'; -import notification from '@/services/notification'; -import template from './permissions-editor.html'; - -const PermissionsEditorComponent = { - template, - bindings: { - resolve: '<', - close: '&', - dismiss: '&', - }, - controller($http, User) { - 'ngInject'; - - this.grantees = []; - this.newGrantees = {}; - this.aclUrl = this.resolve.aclUrl.url; - this.owner = this.resolve.owner; - - // List users that are granted permissions - const loadGrantees = () => { - $http.get(this.aclUrl).success((result) => { - this.grantees = []; - - each(result, (grantees, accessType) => { - grantees.forEach((grantee) => { - grantee.access_type = accessType; - this.grantees.push(grantee); - }); - }); - }); - }; - - loadGrantees(); - - // Search for user - this.findUser = (search) => { - if (search === '') { - this.foundUsers = []; - return; - } - - User.query({ q: search }, (response) => { - const users = filter(response.results, u => u.id !== this.owner.id); - const existingIds = this.grantees.map(m => m.id); - users.forEach((user) => { - user.alreadyGrantee = includes(existingIds, user.id); - }); - this.foundUsers = users; - }); - }; - - // Add new user to grantees list - this.addGrantee = (user) => { - this.newGrantees = {}; - const body = { access_type: 'modify', user_id: user.id }; - $http.post(this.aclUrl, body).success(() => { - user.alreadyGrantee = true; - loadGrantees(); - }).catch((error) => { - if (error.status === 403) { - notification.error('You cannot add a user to this dashboard.', 'Ask the dashboard owner to grant them permissions.'); - } else { - notification.error('Something went wrong.'); - } - }); - }; - - // Remove user from grantees list - this.removeGrantee = (user) => { - const body = { access_type: 'modify', user_id: user.id }; - $http({ - url: this.aclUrl, - method: 'DELETE', - data: body, - headers: { 'Content-Type': 'application/json' }, - }).success(() => { - this.grantees = this.grantees.filter(m => m !== user); - - if (this.foundUsers) { - this.foundUsers.forEach((u) => { - if (u.id === user.id) { - u.alreadyGrantee = false; - } - }); - } - }); - }; - }, -}; - -export default function init(ngModule) { - ngModule.component('permissionsEditor', PermissionsEditorComponent); -} - -init.init = true; diff --git a/client/app/components/permissions-editor/permissions-editor.html b/client/app/components/permissions-editor/permissions-editor.html deleted file mode 100644 index 8470e1c8d4..0000000000 --- a/client/app/components/permissions-editor/permissions-editor.html +++ /dev/null @@ -1,47 +0,0 @@ - - diff --git a/client/app/pages/dashboards/dashboard.js b/client/app/pages/dashboards/dashboard.js index 8346d929de..9421ba5346 100644 --- a/client/app/pages/dashboards/dashboard.js +++ b/client/app/pages/dashboards/dashboard.js @@ -12,6 +12,7 @@ import template from './dashboard.html'; import ShareDashboardDialog from './ShareDashboardDialog'; import AddWidgetDialog from '@/components/dashboards/AddWidgetDialog'; import TextboxDialog from '@/components/dashboards/TextboxDialog'; +import PermissionsEditorDialog from '@/components/permissions-editor/PermissionsEditorDialog'; import notification from '@/services/notification'; import './dashboard.less'; @@ -250,12 +251,11 @@ function DashboardCtrl( }; this.showManagePermissionsModal = () => { - $uibModal.open({ - component: 'permissionsEditor', - resolve: { - aclUrl: { url: `api/dashboards/${this.dashboard.id}/acl` }, - owner: this.dashboard.user, - }, + const aclUrl = `api/dashboards/${this.dashboard.id}/acl`; + PermissionsEditorDialog.showModal({ + aclUrl, + context: 'query', + owner: this.dashboard.user, }); }; From 29acc69c91e8a5454f86448e31963d143d566d18 Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Sat, 26 Oct 2019 14:58:55 -0300 Subject: [PATCH 4/8] Undo changes in SelectItemsDialog --- client/app/components/SelectItemsDialog.jsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/client/app/components/SelectItemsDialog.jsx b/client/app/components/SelectItemsDialog.jsx index ace2ef379e..f54dc27773 100644 --- a/client/app/components/SelectItemsDialog.jsx +++ b/client/app/components/SelectItemsDialog.jsx @@ -29,7 +29,6 @@ class SelectItemsDialog extends React.Component { renderItem: PropTypes.func, // right list; args/results save as for `renderItem`. if not specified - `renderItem` will be used renderStagedItem: PropTypes.func, - defaultSelectedItem: PropTypes.func, // (item) => bool save: PropTypes.func, // (selectedItems[]) => Promise width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), extraFooterContent: PropTypes.node, @@ -43,7 +42,6 @@ class SelectItemsDialog extends React.Component { itemKey: item => item.id, renderItem: () => '', renderStagedItem: null, // hidden by default - defaultSelectedItem: () => false, // no items are selected by default save: items => items, width: '80%', extraFooterContent: null, @@ -59,7 +57,7 @@ class SelectItemsDialog extends React.Component { }; // eslint-disable-next-line react/sort-comp - loadItems = (searchTerm = '', assignSelected = false) => { + loadItems = (searchTerm = '') => { this.setState({ searchTerm, loading: true }, () => { this.props.searchItems(searchTerm) .then((items) => { @@ -67,9 +65,6 @@ class SelectItemsDialog extends React.Component { if (this.state.searchTerm === searchTerm) { this.setState({ items, loading: false }); } - if (assignSelected) { - this.setState({ selected: filter(items, this.props.defaultSelectedItem) }); - } }) .catch(() => { if (this.state.searchTerm === searchTerm) { @@ -82,7 +77,7 @@ class SelectItemsDialog extends React.Component { search = debounce(this.loadItems, 200); componentDidMount() { - this.loadItems('', true); + this.loadItems(); } isSelected(item) { From c32dc7a170c7061ae223167b62c5052b2d485c69 Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Sun, 27 Oct 2019 12:43:41 -0300 Subject: [PATCH 5/8] CR changes - omit users with permissions - remove margin from Tag - use small loading indicator --- .../PermissionsEditorDialog.jsx | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/client/app/components/permissions-editor/PermissionsEditorDialog.jsx b/client/app/components/permissions-editor/PermissionsEditorDialog.jsx index 46b91461a5..baa4c691c3 100644 --- a/client/app/components/permissions-editor/PermissionsEditorDialog.jsx +++ b/client/app/components/permissions-editor/PermissionsEditorDialog.jsx @@ -8,7 +8,6 @@ import Select from 'antd/lib/select'; import Tag from 'antd/lib/tag'; import Tooltip from 'antd/lib/tooltip'; import { wrap as wrapDialog, DialogPropType } from '@/components/DialogWrapper'; -import LoadingState from '@/components/items-list/components/LoadingState'; import { $http } from '@/services/ng'; import { toHuman } from '@/filters'; import HelpTrigger from '@/components/HelpTrigger'; @@ -63,7 +62,7 @@ function PermissionsEditorDialogHeader({ context }) { PermissionsEditorDialogHeader.propTypes = { context: PropTypes.oneOf(['query', 'dashboard']) }; PermissionsEditorDialogHeader.defaultProps = { context: 'query' }; -function UserSelect({ onSelect, previewCardAddon, isUserDisabled }) { +function UserSelect({ onSelect, shouldShowUser }) { const [loadingUsers, setLoadingUsers] = useState(true); const [users, setUsers] = useState([]); const [searchTerm, setSearchTerm] = useState(''); @@ -93,11 +92,9 @@ function UserSelect({ onSelect, previewCardAddon, isUserDisabled }) { getPopupContainer={trigger => trigger.parentNode} onSelect={onSelect} > - {users.map(user => ( - ))} @@ -106,10 +103,9 @@ function UserSelect({ onSelect, previewCardAddon, isUserDisabled }) { UserSelect.propTypes = { onSelect: PropTypes.func, - previewCardAddon: PropTypes.func, - isUserDisabled: PropTypes.func, + shouldShowUser: PropTypes.func, }; -UserSelect.defaultProps = { onSelect: () => {}, previewCardAddon: () => null, isUserDisabled: () => false }; +UserSelect.defaultProps = { onSelect: () => {}, shouldShowUser: () => true }; function PermissionsEditorDialog({ dialog, owner, context, aclUrl }) { const [loadingGrantees, setLoadingGrantees] = useState(true); @@ -141,32 +137,32 @@ function PermissionsEditorDialog({ dialog, owner, context, aclUrl }) { > addPermission(userId).then(loadUsersWithPermissions)} - previewCardAddon={user => (userHasPermission(user) ? '(already has permission)' : null)} - isUserDisabled={user => userHasPermission(user)} + shouldShowUser={user => !userHasPermission(user)} /> -
Users with permissions
- {!loadingGrantees ? ( -
- ( - - - {user.id === owner.id ? (Owner) : ( - - removePermission(user.id).then(loadUsersWithPermissions)} - /> - - )} - - - )} - /> -
- ) : } +
+
Users with permissions
+ {loadingGrantees && } +
+
+ ( + + + {user.id === owner.id ? (Owner) : ( + + removePermission(user.id).then(loadUsersWithPermissions)} + /> + + )} + + + )} + /> +
); } From e29509313aa5d3dcaf6620a5a523289a6b1549b6 Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Sun, 27 Oct 2019 12:45:55 -0300 Subject: [PATCH 6/8] owner -> author --- .../permissions-editor/PermissionsEditorDialog.jsx | 10 +++++----- client/app/pages/dashboards/dashboard.js | 2 +- client/app/pages/queries/view.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/app/components/permissions-editor/PermissionsEditorDialog.jsx b/client/app/components/permissions-editor/PermissionsEditorDialog.jsx index baa4c691c3..11733eb9ab 100644 --- a/client/app/components/permissions-editor/PermissionsEditorDialog.jsx +++ b/client/app/components/permissions-editor/PermissionsEditorDialog.jsx @@ -107,7 +107,7 @@ UserSelect.propTypes = { }; UserSelect.defaultProps = { onSelect: () => {}, shouldShowUser: () => true }; -function PermissionsEditorDialog({ dialog, owner, context, aclUrl }) { +function PermissionsEditorDialog({ dialog, author, context, aclUrl }) { const [loadingGrantees, setLoadingGrantees] = useState(true); const [grantees, setGrantees] = useState([]); const { loadGrantees, addPermission, removePermission } = useGrantees(aclUrl); @@ -120,7 +120,7 @@ function PermissionsEditorDialog({ dialog, owner, context, aclUrl }) { }, []); const userHasPermission = useCallback( - user => (user.id === owner.id || !!get(find(grantees, { id: user.id }), 'accessType')), + user => (user.id === author.id || !!get(find(grantees, { id: user.id }), 'accessType')), [grantees], ); @@ -146,11 +146,11 @@ function PermissionsEditorDialog({ dialog, owner, context, aclUrl }) {
( - {user.id === owner.id ? (Owner) : ( + {user.id === author.id ? (Author) : ( Date: Mon, 28 Oct 2019 00:14:28 -0300 Subject: [PATCH 7/8] query -> dashboard Co-Authored-By: Arik Fraimovich --- client/app/pages/dashboards/dashboard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/app/pages/dashboards/dashboard.js b/client/app/pages/dashboards/dashboard.js index 47ec23e3d1..6a5ca7e66c 100644 --- a/client/app/pages/dashboards/dashboard.js +++ b/client/app/pages/dashboards/dashboard.js @@ -254,7 +254,7 @@ function DashboardCtrl( const aclUrl = `api/dashboards/${this.dashboard.id}/acl`; PermissionsEditorDialog.showModal({ aclUrl, - context: 'query', + context: 'dashboard', author: this.dashboard.user, }); }; From 26032d4dae0969f5f025ac6c3caf89c0023f3ba3 Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Mon, 28 Oct 2019 09:42:23 -0300 Subject: [PATCH 8/8] Margin updates --- .../components/permissions-editor/PermissionsEditorDialog.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/app/components/permissions-editor/PermissionsEditorDialog.jsx b/client/app/components/permissions-editor/PermissionsEditorDialog.jsx index 11733eb9ab..caa2112f86 100644 --- a/client/app/components/permissions-editor/PermissionsEditorDialog.jsx +++ b/client/app/components/permissions-editor/PermissionsEditorDialog.jsx @@ -139,11 +139,11 @@ function PermissionsEditorDialog({ dialog, author, context, aclUrl }) { onSelect={userId => addPermission(userId).then(loadUsersWithPermissions)} shouldShowUser={user => !userHasPermission(user)} /> -
+
Users with permissions
{loadingGrantees && }
-
+