From: Pawel Kowalczyk Date: Wed, 5 Dec 2018 10:46:34 +0000 (+0100) Subject: context-menu-for-user-data-explorer X-Git-Tag: 1.4.0~93^2~7 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/fc3370a1203ae0b83a13ef6a958219cc722cfe75 context-menu-for-user-data-explorer Feature #14504 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx index 3b09b5ba..69f93dda 100644 --- a/src/components/data-explorer/data-explorer.tsx +++ b/src/components/data-explorer/data-explorer.tsx @@ -4,13 +4,13 @@ import * as React from 'react'; import { Grid, Paper, Toolbar, StyleRulesCallback, withStyles, WithStyles, TablePagination, IconButton, Tooltip } from '@material-ui/core'; -import { ColumnSelector } from "../column-selector/column-selector"; -import { DataTable, DataColumns } from "../data-table/data-table"; -import { DataColumn, SortDirection } from "../data-table/data-column"; -import { SearchInput } from '../search-input/search-input'; +import { ColumnSelector } from "~/components/column-selector/column-selector"; +import { DataTable, DataColumns } from "~/components/data-table/data-table"; +import { DataColumn, SortDirection } from "~/components/data-table/data-column"; +import { SearchInput } from '~/components/search-input/search-input'; import { ArvadosTheme } from "~/common/custom-theme"; import { createTree } from '~/models/tree'; -import { DataTableFilters } from '../data-table-filters/data-table-filters-tree'; +import { DataTableFilters } from '~/components/data-table-filters/data-table-filters-tree'; import { MoreOptionsIcon } from '~/components/icon/icon'; type CssRules = 'searchBox' | "toolbar" | "footer" | "root" | 'moreOptionsButton'; diff --git a/src/index.tsx b/src/index.tsx index d8385967..96df16cf 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,31 +5,31 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Provider } from "react-redux"; -import { MainPanel } from './views/main-panel/main-panel'; -import './index.css'; +import { MainPanel } from '~/views/main-panel/main-panel'; +import '~/index.css'; import { Route, Switch } from 'react-router'; import createBrowserHistory from "history/createBrowserHistory"; import { History } from "history"; -import { configureStore, RootStore } from './store/store'; +import { configureStore, RootStore } from '~/store/store'; import { ConnectedRouter } from "react-router-redux"; -import { ApiToken } from "./views-components/api-token/api-token"; -import { initAuth } from "./store/auth/auth-action"; -import { createServices } from "./services/services"; +import { ApiToken } from "~/views-components/api-token/api-token"; +import { initAuth } from "~/store/auth/auth-action"; +import { createServices } from "~/services/services"; import { MuiThemeProvider } from '@material-ui/core/styles'; -import { CustomTheme } from './common/custom-theme'; -import { fetchConfig } from './common/config'; -import { addMenuActionSet, ContextMenuKind } from './views-components/context-menu/context-menu'; -import { rootProjectActionSet } from "./views-components/context-menu/action-sets/root-project-action-set"; -import { projectActionSet } from "./views-components/context-menu/action-sets/project-action-set"; -import { resourceActionSet } from './views-components/context-menu/action-sets/resource-action-set'; -import { favoriteActionSet } from "./views-components/context-menu/action-sets/favorite-action-set"; -import { collectionFilesActionSet } from './views-components/context-menu/action-sets/collection-files-action-set'; -import { collectionFilesItemActionSet } from './views-components/context-menu/action-sets/collection-files-item-action-set'; -import { collectionFilesNotSelectedActionSet } from './views-components/context-menu/action-sets/collection-files-not-selected-action-set'; -import { collectionActionSet } from './views-components/context-menu/action-sets/collection-action-set'; -import { collectionResourceActionSet } from './views-components/context-menu/action-sets/collection-resource-action-set'; -import { processActionSet } from './views-components/context-menu/action-sets/process-action-set'; -import { loadWorkbench } from './store/workbench/workbench-actions'; +import { CustomTheme } from '~/common/custom-theme'; +import { fetchConfig } from '~/common/config'; +import { addMenuActionSet, ContextMenuKind } from '~/views-components/context-menu/context-menu'; +import { rootProjectActionSet } from "~/views-components/context-menu/action-sets/root-project-action-set"; +import { projectActionSet } from "~/views-components/context-menu/action-sets/project-action-set"; +import { resourceActionSet } from '~/views-components/context-menu/action-sets/resource-action-set'; +import { favoriteActionSet } from "~/views-components/context-menu/action-sets/favorite-action-set"; +import { collectionFilesActionSet } from '~/views-components/context-menu/action-sets/collection-files-action-set'; +import { collectionFilesItemActionSet } from '~/views-components/context-menu/action-sets/collection-files-item-action-set'; +import { collectionFilesNotSelectedActionSet } from '~/views-components/context-menu/action-sets/collection-files-not-selected-action-set'; +import { collectionActionSet } from '~/views-components/context-menu/action-sets/collection-action-set'; +import { collectionResourceActionSet } from '~/views-components/context-menu/action-sets/collection-resource-action-set'; +import { processActionSet } from '~/views-components/context-menu/action-sets/process-action-set'; +import { loadWorkbench } from '~/store/workbench/workbench-actions'; import { Routes } from '~/routes/routes'; import { trashActionSet } from "~/views-components/context-menu/action-sets/trash-action-set"; import { ServiceRepository } from '~/services/services'; @@ -53,6 +53,7 @@ import { sshKeyActionSet } from '~/views-components/context-menu/action-sets/ssh import { keepServiceActionSet } from '~/views-components/context-menu/action-sets/keep-service-action-set'; import { loadVocabulary } from '~/store/vocabulary/vocabulary-actions'; import { virtualMachineActionSet } from '~/views-components/context-menu/action-sets/virtual-machine-action-set'; +import { userActionSet } from '~/views-components/context-menu/action-sets/user-action-set'; console.log(`Starting arvados [${getBuildInfo()}]`); @@ -73,6 +74,7 @@ addMenuActionSet(ContextMenuKind.REPOSITORY, repositoryActionSet); addMenuActionSet(ContextMenuKind.SSH_KEY, sshKeyActionSet); addMenuActionSet(ContextMenuKind.VIRTUAL_MACHINE, virtualMachineActionSet); addMenuActionSet(ContextMenuKind.KEEP_SERVICE, keepServiceActionSet); +addMenuActionSet(ContextMenuKind.USER, userActionSet); fetchConfig() .then(({ config, apiHost }) => { diff --git a/src/store/advanced-tab/advanced-tab.ts b/src/store/advanced-tab/advanced-tab.ts index 92d14d7b..67d13946 100644 --- a/src/store/advanced-tab/advanced-tab.ts +++ b/src/store/advanced-tab/advanced-tab.ts @@ -72,7 +72,8 @@ enum ResourcePrefix { REPOSITORIES = 'repositories', AUTORIZED_KEYS = 'authorized_keys', VIRTUAL_MACHINES = 'virtual_machines', - KEEP_SERVICES = 'keep_services' + KEEP_SERVICES = 'keep_services', + USERS = 'users' } enum KeepServiceData { @@ -80,9 +81,14 @@ enum KeepServiceData { CREATED_AT = 'created_at' } -type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | VirtualMachineData | KeepServiceData; +enum UserData { + USER = 'user', + USERNAME = 'username' +} + +type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | VirtualMachineData | KeepServiceData | UserData; type AdvanceResourcePrefix = GroupContentsResourcePrefix | ResourcePrefix; -type AdvanceResponseData = ContainerRequestResource | ProjectResource | CollectionResource | RepositoryResource | SshKeyResource | VirtualMachinesResource | KeepServiceResource | undefined; +type AdvanceResponseData = ContainerRequestResource | ProjectResource | CollectionResource | RepositoryResource | SshKeyResource | VirtualMachinesResource | KeepServiceResource | UserResource | undefined; export const openAdvancedTabDialog = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { @@ -193,6 +199,27 @@ export const openAdvancedTabDialog = (uuid: string) => }); dispatch(initAdvancedTabDialog(advanceDataKeepService)); break; + case ResourceKind.USER: + const { resources } = getState(); + const data = getResource(uuid)(resources); + const metadata = await services.linkService.list({ + filters: new FilterBuilder() + .addEqual('headUuid', uuid) + .getFilters() + }); + const advanceDataUser = advancedTabData({ + uuid, + metadata, + user: '', + apiResponseKind: userApiResponse, + data, + resourceKind: UserData.USER, + resourcePrefix: ResourcePrefix.USERS, + resourceKindProperty: UserData.USERNAME, + property: data!.username + }); + dispatch(initAdvancedTabDialog(advanceDataUser)); + break; default: dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Could not open advanced tab for this resource.", hideDuration: 2000, kind: SnackbarKind.ERROR })); } @@ -440,5 +467,29 @@ const keepServiceApiResponse = (apiResponse: KeepServiceResource) => { "created_at": "${createdAt}", "read_only": "${stringify(readOnly)}"`; + return response; +}; + +const userApiResponse = (apiResponse: UserResource) => { + const { + uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, + email, firstName, lastName, identityUrl, isActive, isAdmin, prefs, defaultOwnerUuid, username + } = apiResponse; + const response = `"uuid": "${uuid}", +"owner_uuid": "${ownerUuid}", +"created_at": "${createdAt}", +"modified_by_client_uuid": ${stringify(modifiedByClientUuid)}, +"modified_by_user_uuid": ${stringify(modifiedByUserUuid)}, +"modified_at": ${stringify(modifiedAt)}, +"email": "${email}", +"first_name": "${firstName}", +"last_name": "${stringify(lastName)}", +"identity_url": "${identityUrl}", +"is_active": "${isActive}, +"is_admin": "${isAdmin}, +"prefs": "${stringifyObject(prefs)}, +"default_owner_uuid": "${defaultOwnerUuid}, +"username": "${username}"`; + return response; }; \ No newline at end of file diff --git a/src/store/users/users-actions.ts b/src/store/users/users-actions.ts index 8ec373a8..43d9e6f3 100644 --- a/src/store/users/users-actions.ts +++ b/src/store/users/users-actions.ts @@ -13,6 +13,7 @@ import { startSubmit, reset, stopSubmit } from "redux-form"; import { getCommonResourceServiceError, CommonResourceServiceError } from "~/services/common-service/common-resource-service"; import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions'; import { UserResource } from "~/models/user"; +import { getResource } from '~/store/resources/resources'; export const usersPanelActions = unionize({ SET_USERS: ofType(), @@ -27,8 +28,9 @@ export const USER_REMOVE_DIALOG = 'repositoryRemoveDialog'; export const openUserAttributes = (uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const repositoryData = getState().repositories.items.find(it => it.uuid === uuid); - dispatch(dialogActions.OPEN_DIALOG({ id: USER_ATTRIBUTES_DIALOG, data: { repositoryData } })); + const { resources } = getState(); + const data = getResource(uuid)(resources); + dispatch(dialogActions.OPEN_DIALOG({ id: USER_ATTRIBUTES_DIALOG, data })); }; export const openUserCreateDialog = () => diff --git a/src/views-components/context-menu/action-sets/user-action-set.ts b/src/views-components/context-menu/action-sets/user-action-set.ts new file mode 100644 index 00000000..e06a7c21 --- /dev/null +++ b/src/views-components/context-menu/action-sets/user-action-set.ts @@ -0,0 +1,35 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { ContextMenuActionSet } from "~/views-components/context-menu/context-menu-action-set"; +import { AdvancedIcon, ProjectIcon, AttributesIcon, ShareIcon } from "~/components/icon/icon"; +import { openAdvancedTabDialog } from '~/store/advanced-tab/advanced-tab'; +import { openUserAttributes } from "~/store/users/users-actions"; + +export const userActionSet: ContextMenuActionSet = [[{ + name: "Attributes", + icon: AttributesIcon, + execute: (dispatch, { uuid }) => { + dispatch(openUserAttributes(uuid)); + } +}, { + name: "Project", + icon: ProjectIcon, + execute: (dispatch, { uuid }) => { + dispatch(openAdvancedTabDialog(uuid)); + } +}, { + name: "Advanced", + icon: AdvancedIcon, + execute: (dispatch, { uuid }) => { + dispatch(openAdvancedTabDialog(uuid)); + } +}, +{ + name: "Manage", + icon: ShareIcon, + execute: (dispatch, { uuid }) => { + dispatch(openAdvancedTabDialog(uuid)); + } +}]]; diff --git a/src/views-components/context-menu/context-menu.tsx b/src/views-components/context-menu/context-menu.tsx index 5f321bfe..29b8afbc 100644 --- a/src/views-components/context-menu/context-menu.tsx +++ b/src/views-components/context-menu/context-menu.tsx @@ -72,5 +72,6 @@ export enum ContextMenuKind { REPOSITORY = "Repository", SSH_KEY = "SshKey", VIRTUAL_MACHINE = "VirtualMachine", - KEEP_SERVICE = "KeepService" + KEEP_SERVICE = "KeepService", + USER = "User" } diff --git a/src/views-components/user-dialog/attributes-dialog.tsx b/src/views-components/user-dialog/attributes-dialog.tsx new file mode 100644 index 00000000..5f711de7 --- /dev/null +++ b/src/views-components/user-dialog/attributes-dialog.tsx @@ -0,0 +1,97 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from "react"; +import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography, Grid } from "@material-ui/core"; +import { WithDialogProps } from "~/store/dialog/with-dialog"; +import { withDialog } from '~/store/dialog/with-dialog'; +import { WithStyles, withStyles } from '@material-ui/core/styles'; +import { ArvadosTheme } from '~/common/custom-theme'; +import { compose } from "redux"; +import { USER_ATTRIBUTES_DIALOG } from "~/store/users/users-actions"; +import { UserResource } from "~/models/user"; + +type CssRules = 'rightContainer' | 'leftContainer' | 'spacing'; + +const styles = withStyles((theme: ArvadosTheme) => ({ + rightContainer: { + textAlign: 'right', + paddingRight: theme.spacing.unit * 2, + color: theme.palette.grey["500"] + }, + leftContainer: { + textAlign: 'left', + paddingLeft: theme.spacing.unit * 2 + }, + spacing: { + paddingTop: theme.spacing.unit * 2 + }, +})); + +interface UserAttributesDataProps { + data: UserResource; +} + +type UserAttributesProps = UserAttributesDataProps & WithStyles; + +export const UserAttributesDialog = compose( + withDialog(USER_ATTRIBUTES_DIALOG), + styles)( + (props: WithDialogProps & UserAttributesProps) => + + Attributes + + + {props.data && attributes(props.data, props.classes)} + + + + + + + ); + +const attributes = (user: UserResource, classes: any) => { + const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, firstName, lastName, href, identityUrl, username } = user; + return ( + + + + First name + Last name + Owner uuid + Created at + Modified at + Modified by user uuid + Modified by client uuid + uuid + Href + Identity url + Username + + + {firstName} + {lastName} + {ownerUuid} + {createdAt} + {modifiedAt} + {modifiedByUserUuid} + {modifiedByClientUuid} + {uuid} + {href} + {identityUrl} + {username} + + + + ); +}; diff --git a/src/views/user-panel/user-panel.tsx b/src/views/user-panel/user-panel.tsx index 4b9a3396..ceaba8cb 100644 --- a/src/views/user-panel/user-panel.tsx +++ b/src/views/user-panel/user-panel.tsx @@ -133,7 +133,6 @@ export const UserPanel = compose( })))( class extends React.Component { render() { - console.log(this.props.resources); return + ); \ No newline at end of file