Skip to content

Commit

Permalink
New permissions pages
Browse files Browse the repository at this point in the history
  • Loading branch information
alxnddr committed Aug 13, 2021
1 parent ffebacf commit 3ce3c35
Show file tree
Hide file tree
Showing 69 changed files with 3,299 additions and 3,294 deletions.
Expand Up @@ -27,6 +27,7 @@ import Dimension from "metabase-lib/lib/Dimension";

import _ from "underscore";
import { jt, t } from "ttag";
import { getParentPath } from "metabase/hoc/ModalRoute";

const mapStateToProps = () => ({});
const mapDispatchToProps = {
Expand Down Expand Up @@ -91,15 +92,8 @@ export default class GTAPModal extends React.Component {
}

close = () => {
const {
push,
params: { databaseId, schemaName },
} = this.props;
push(
`/admin/permissions/databases/${databaseId}` +
(schemaName ? `/schemas/${encodeURIComponent(schemaName)}` : ``) +
`/tables`,
);
const { push, route, location } = this.props;
return push(getParentPath(route, location));
};

_getCanonicalGTAP() {
Expand Down
67 changes: 39 additions & 28 deletions enterprise/frontend/src/metabase-enterprise/sandboxes/index.js
@@ -1,6 +1,7 @@
import {
PLUGIN_ADMIN_USER_FORM_FIELDS,
PLUGIN_ADMIN_PERMISSIONS_TABLE_ROUTES,
PLUGIN_ADMIN_PERMISSIONS_TABLE_GROUP_ROUTES,
PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_OPTIONS,
PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_ACTIONS,
PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_POST_ACTION,
Expand All @@ -12,43 +13,46 @@ import { push } from "react-router-redux";
import { t } from "ttag";

import { hasPremiumFeature } from "metabase-enterprise/settings";
import { color, alpha } from "metabase/lib/colors";
import { color } from "metabase/lib/colors";

import { ModalRoute } from "metabase/hoc/ModalRoute";
import LoginAttributesWidget from "./components/LoginAttributesWidget";
import GTAPModal from "./components/GTAPModal";

const OPTION_BLUE = {
iconColor: color("brand"),
bgColor: alpha(color("brand"), 0.15),
};

const OPTION_SEGMENTED = {
...OPTION_BLUE,
label: t`Sandboxed`,
value: "controlled",
title: t`Grant sandboxed access`,
tooltip: t`Sandboxed access`,
icon: "permissions_limited",
iconColor: color("brand"),
};

const getEditSegementedAccessUrl = (
const getDatabaseViewSandboxModalUrl = ({
groupId,
{ databaseId, schemaName, tableId },
) =>
`/admin/permissions` +
`/databases/${databaseId}` +
(schemaName ? `/schemas/${encodeURIComponent(schemaName)}` : "") +
`/tables/${tableId}/segmented/group/${groupId}`;
databaseId,
schemaName,
tableId,
}) =>
`/admin/permissions/data/database/${databaseId}/schema/${encodeURIComponent(
schemaName,
)}/table/${tableId}/segmented/group/${groupId}`;

const getEditSegementedAccessAction = (groupId, entityId) => ({
...OPTION_BLUE,
title: t`Edit sandboxed access`,
icon: "pencil",
value: push(getEditSegementedAccessUrl(groupId, entityId)),
});
const getGroupViewSandboxModalUrl = ({
groupId,
databaseId,
schemaName,
tableId,
}) =>
`/admin/permissions/data/group/${groupId}/database/${databaseId}/schema/${encodeURIComponent(
schemaName,
)}/${tableId}/segmented`;

const getEditSegmentedAcessPostAction = (groupId, entityId) =>
push(getEditSegementedAccessUrl(groupId, entityId));
const getEditSegementedAccessUrl = (params, view) =>
view === "database"
? getDatabaseViewSandboxModalUrl(params)
: getGroupViewSandboxModalUrl(params);

const getEditSegmentedAcessPostAction = (params, view) =>
push(getEditSegementedAccessUrl(params, view));

if (hasPremiumFeature("sandboxes")) {
PLUGIN_ADMIN_USER_FORM_FIELDS.push({
Expand All @@ -57,12 +61,19 @@ if (hasPremiumFeature("sandboxes")) {
type: LoginAttributesWidget,
});
PLUGIN_ADMIN_PERMISSIONS_TABLE_ROUTES.push(
<ModalRoute path=":tableId/segmented/group/:groupId" modal={GTAPModal} />,
<ModalRoute path=":tableId/segmented" modal={GTAPModal} />,
);
PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_OPTIONS.push(OPTION_SEGMENTED);
PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_ACTIONS["controlled"].push(
getEditSegementedAccessAction,
PLUGIN_ADMIN_PERMISSIONS_TABLE_GROUP_ROUTES.push(
<ModalRoute path="segmented/group/:groupId" modal={GTAPModal} />,
);
PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_OPTIONS.push(OPTION_SEGMENTED);
PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_ACTIONS["controlled"].push({
label: t`Edit sandboxed access`,
iconColor: color("brand"),
icon: "pencil",
actionCreator: (groupId, entityId, view) =>
push(getEditSegementedAccessUrl({ ...entityId, groupId }, view)),
});
PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_POST_ACTION[
"controlled"
] = getEditSegmentedAcessPostAction;
Expand Down
Expand Up @@ -9,7 +9,7 @@ import {
} from "metabase/plugins";

import MetabaseSettings from "metabase/lib/settings";
import CollectionPermissionsModal from "metabase/admin/permissions/containers/CollectionPermissionsModal";
import CollectionPermissionsModal from "metabase/admin/permissions/components/CollectionPermissionsModal/CollectionPermissionsModal";
import Modal from "metabase/components/Modal";

import CollectionRow from "./components/CollectionRow";
Expand Down
@@ -0,0 +1,170 @@
import React, { useEffect, useCallback } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { t } from "ttag";
import _ from "underscore";

import * as Urls from "metabase/lib/urls";

import Collections from "metabase/entities/collections";
import SnippetCollections from "metabase/entities/snippet-collections";

import { isPersonalCollectionChild } from "metabase/collections/utils";

import ModalContent from "metabase/components/ModalContent";
import Button from "metabase/components/Button";
import Link from "metabase/components/Link";

import { PermissionsTable } from "../PermissionsTable";
import Groups from "metabase/entities/groups";
import {
getDiff,
getIsDirty,
getCollectionsPermissionEditor,
} from "../../selectors/collection-permissions";
import {
initializeCollectionPermissions,
updateCollectionPermission,
saveCollectionPermissions,
} from "../../permissions";
import { permissionEditorPropTypes } from "../PermissionsEditor/PermissionsEditor";

const getDefaultTitle = namespace =>
namespace === "snippets"
? t`Permissions for this folder`
: t`Permissions for this collection`;

const propTypes = {
permissionEditor: PropTypes.shape(permissionEditorPropTypes),
namespace: PropTypes.string,
isDirty: PropTypes.bool,
onClose: PropTypes.func.isRequired,
collection: PropTypes.object,
collectionsList: PropTypes.arrayOf(PropTypes.object),
initialize: PropTypes.func.isRequired,
updateCollectionPermission: PropTypes.func.isRequired,
saveCollectionPermissions: PropTypes.func.isRequired,
};

const CollectionPermissionsModal = ({
permissionEditor,
isDirty,
onClose,
namespace,
collection,
collectionsList,

initialize,
updateCollectionPermission,
saveCollectionPermissions,
}) => {
useEffect(() => {
initialize(namespace);
}, [initialize, namespace]);

useEffect(() => {
const isPersonalCollectionLoaded =
collection &&
Array.isArray(collectionsList) &&
(collection.personal_owner_id ||
isPersonalCollectionChild(collection, collectionsList));

if (isPersonalCollectionLoaded || collection.archived) {
onClose();
}
}, [collectionsList, collection, onClose]);

const handleSave = async () => {
await saveCollectionPermissions(namespace);
onClose();
};

const modalTitle = collection?.name
? t`Permissions for ${collection.name}`
: getDefaultTitle(namespace);

const handlePermissionChange = useCallback(
(item, _permission, value, toggleState) => {
updateCollectionPermission({
groupId: item.id,
collection,
value,
shouldPropagate: toggleState,
});
},
[collection, updateCollectionPermission],
);

return (
<ModalContent
title={modalTitle}
onClose={onClose}
footer={[
...(namespace === "snippets"
? []
: [
<Link
key="all-permissions"
className="link"
to="/admin/permissions/collections"
>
{t`See all collection permissions`}
</Link>,
]),
<Button key="cancel" onClick={onClose}>{t`Cancel`}</Button>,
<Button key="save" primary disabled={!isDirty} onClick={handleSave}>
{t`Save`}
</Button>,
]}
>
<div className="relative" style={{ height: "50vh" }}>
{permissionEditor && (
<PermissionsTable
{...permissionEditor}
onChange={handlePermissionChange}
/>
)}
</div>
</ModalContent>
);
};

CollectionPermissionsModal.propTypes = propTypes;

const getCollectionEntity = props =>
props.namespace === "snippets" ? SnippetCollections : Collections;

const mapStateToProps = (state, props) => {
const collectionId = Urls.extractCollectionId(props.params.slug);
return {
permissionEditor: getCollectionsPermissionEditor(state, {
namespace: props.namespace,
params: { collectionId },
}),
collection: getCollectionEntity(props).selectors.getObject(state, {
entityId: collectionId,
}),
collectionsList: Collections.selectors.getList(state, {
entityQuery: { tree: true },
}),
diff: getDiff(state, props),
isDirty: getIsDirty(state, props),
};
};

const mapDispatchToProps = {
initialize: initializeCollectionPermissions,
updateCollectionPermission,
saveCollectionPermissions,
};

export default _.compose(
Collections.loadList({
query: () => ({ tree: true }),
}),
Groups.loadList(),
connect(
mapStateToProps,
mapDispatchToProps,
),
)(CollectionPermissionsModal);

This file was deleted.

0 comments on commit 3ce3c35

Please sign in to comment.