[ARVADOS-WORKBENCH2] created: 1.3.0-78-g1253188
Git user
git at public.curoverse.com
Mon Dec 10 11:34:43 EST 2018
at 12531884583ab85a114eed12f5179ed0b5fb8a88 (commit)
commit 12531884583ab85a114eed12f5179ed0b5fb8a88
Author: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
Date: Mon Dec 10 17:34:32 2018 +0100
Initialize groups panel store and view component
Feature #14505
Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
diff --git a/src/store/groups-panel/groups-panel-actions.ts b/src/store/groups-panel/groups-panel-actions.ts
new file mode 100644
index 0000000..5adce5c
--- /dev/null
+++ b/src/store/groups-panel/groups-panel-actions.ts
@@ -0,0 +1,10 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { bindDataExplorerActions } from "~/store/data-explorer/data-explorer-action";
+
+export const GROUPS_PANEL_ID = "groupsPanel";
+export const GroupsPanelActions = bindDataExplorerActions(GROUPS_PANEL_ID);
+
+export const loadGroupsPanel = () => GroupsPanelActions.REQUEST_ITEMS();
diff --git a/src/store/groups-panel/groups-panel-middleware-service.ts b/src/store/groups-panel/groups-panel-middleware-service.ts
new file mode 100644
index 0000000..9d43b11
--- /dev/null
+++ b/src/store/groups-panel/groups-panel-middleware-service.ts
@@ -0,0 +1,69 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch, MiddlewareAPI } from "redux";
+import { DataExplorerMiddlewareService, listResultsToDataExplorerItemsMeta, dataExplorerToListParams } from "~/store/data-explorer/data-explorer-middleware-service";
+import { RootState } from "~/store/store";
+import { ServiceRepository } from "~/services/services";
+import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
+import { getDataExplorer } from "~/store/data-explorer/data-explorer-reducer";
+import { GroupsPanelActions } from '~/store/groups-panel/groups-panel-actions';
+import { FilterBuilder } from '~/services/api/filter-builder';
+import { updateResources } from '~/store/resources/resources-actions';
+
+export class GroupsPanelMiddlewareService extends DataExplorerMiddlewareService {
+
+ constructor(private services: ServiceRepository, id: string) {
+ super(id);
+ }
+
+ async requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+
+ const dataExplorer = getDataExplorer(api.getState().dataExplorer, this.getId());
+
+ if (!dataExplorer) {
+
+ api.dispatch(groupsPanelDataExplorerIsNotSet());
+
+ } else {
+
+ try {
+
+ const filters = new FilterBuilder()
+ .addEqual('groupClass', null)
+ .getFilters();
+
+ const response = await this.services.groupsService
+ .list({
+ ...dataExplorerToListParams(dataExplorer),
+ filters,
+ });
+
+ api.dispatch(updateResources(response.items));
+
+ api.dispatch(GroupsPanelActions.SET_ITEMS({
+ ...listResultsToDataExplorerItemsMeta(response),
+ items: response.items.map(item => item.uuid),
+ }));
+
+
+ } catch (e) {
+
+ api.dispatch(couldNotFetchFavoritesContents());
+
+ }
+ }
+ }
+}
+
+const groupsPanelDataExplorerIsNotSet = () =>
+ snackbarActions.OPEN_SNACKBAR({
+ message: 'Groups panel is not ready.'
+ });
+
+const couldNotFetchFavoritesContents = () =>
+ snackbarActions.OPEN_SNACKBAR({
+ message: 'Could not fetch groups.',
+ kind: SnackbarKind.ERROR
+ });
diff --git a/src/store/store.ts b/src/store/store.ts
index 2b0ada8..c04789d 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -50,6 +50,8 @@ import { UserMiddlewareService } from '~/store/users/user-panel-middleware-servi
import { USERS_PANEL_ID } from '~/store/users/users-actions';
import { computeNodesReducer } from '~/store/compute-nodes/compute-nodes-reducer';
import { apiClientAuthorizationsReducer } from '~/store/api-client-authorizations/api-client-authorizations-reducer';
+import { GroupsPanelMiddlewareService } from '~/store/groups-panel/groups-panel-middleware-service';
+import { GROUPS_PANEL_ID } from '~/store/groups-panel/groups-panel-actions';
const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
@@ -84,6 +86,9 @@ export function configureStore(history: History, services: ServiceRepository): R
const userPanelMiddleware = dataExplorerMiddleware(
new UserMiddlewareService(services, USERS_PANEL_ID)
);
+ const groupsPanelMiddleware = dataExplorerMiddleware(
+ new GroupsPanelMiddlewareService(services, GROUPS_PANEL_ID)
+ );
const middlewares: Middleware[] = [
routerMiddleware(history),
@@ -94,7 +99,8 @@ export function configureStore(history: History, services: ServiceRepository): R
searchResultsPanelMiddleware,
sharedWithMePanelMiddleware,
workflowPanelMiddleware,
- userPanelMiddleware
+ userPanelMiddleware,
+ groupsPanelMiddleware,
];
const enhancer = composeEnhancers(applyMiddleware(...middlewares));
return createStore(rootReducer, enhancer);
diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts
index e0195f9..2ae5902 100644
--- a/src/store/workbench/workbench-actions.ts
+++ b/src/store/workbench/workbench-actions.ts
@@ -62,6 +62,8 @@ import { loadUsersPanel, userBindedActions } from '~/store/users/users-actions';
import { userPanelColumns } from '~/views/user-panel/user-panel';
import { loadComputeNodesPanel } from '~/store/compute-nodes/compute-nodes-actions';
import { loadApiClientAuthorizationsPanel } from '~/store/api-client-authorizations/api-client-authorizations-actions';
+import * as groupPanelActions from '~/store/groups-panel/groups-panel-actions';
+import { groupsPanelColumns } from '~/views/groups-panel/groups-panel';
export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen';
@@ -96,6 +98,7 @@ export const loadWorkbench = () =>
dispatch(workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns }));
dispatch(searchResultsPanelActions.SET_COLUMNS({ columns: searchResultsPanelColumns }));
dispatch(userBindedActions.SET_COLUMNS({ columns: userPanelColumns }));
+ dispatch(groupPanelActions.GroupsPanelActions.SET_COLUMNS({ columns: groupsPanelColumns }));
dispatch<any>(initSidePanelTree());
if (router.location) {
const match = matchRootRoute(router.location.pathname);
@@ -448,6 +451,7 @@ export const loadApiClientAuthorizations = handleFirstTimeLoad(
export const loadGroupsPanel = handleFirstTimeLoad(
(dispatch: Dispatch<any>) => {
dispatch(setBreadcrumbs([{ label: 'Groups' }]));
+ dispatch(groupPanelActions.loadGroupsPanel());
});
const finishLoadingProject = (project: GroupContentsResource | string) =>
diff --git a/src/views/groups-panel/groups-panel.tsx b/src/views/groups-panel/groups-panel.tsx
new file mode 100644
index 0000000..c404f1e
--- /dev/null
+++ b/src/views/groups-panel/groups-panel.tsx
@@ -0,0 +1,79 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import withStyles from "@material-ui/core/styles/withStyles";
+import { DispatchProp, connect } from 'react-redux';
+import { RouteComponentProps } from 'react-router';
+import { StyleRulesCallback, WithStyles, Typography } from "@material-ui/core";
+
+import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
+import { DataColumns } from '~/components/data-table/data-table';
+import { RootState } from '~/store/store';
+import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
+import { ContainerRequestState } from '~/models/container-request';
+import { SortDirection } from '~/components/data-table/data-column';
+import { ResourceKind, Resource } from '~/models/resource';
+import { ResourceFileSize, ResourceLastModifiedDate, ProcessStatus, ResourceType, ResourceOwner } from '~/views-components/data-explorer/renderers';
+import { ProjectIcon } from '~/components/icon/icon';
+import { ResourceName } from '~/views-components/data-explorer/renderers';
+import { ResourcesState, getResource } from '~/store/resources/resources';
+import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
+import { resourceKindToContextMenuKind, openContextMenu } from '~/store/context-menu/context-menu-actions';
+import { ProjectResource } from '~/models/project';
+import { navigateTo } from '~/store/navigation/navigation-action';
+import { getProperty } from '~/store/properties/properties';
+import { PROJECT_PANEL_CURRENT_UUID } from '~/store/project-panel/project-panel-action';
+import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
+import { ArvadosTheme } from "~/common/custom-theme";
+import { createTree } from '~/models/tree';
+import { getInitialResourceTypeFilters } from '~/store/resource-type-filters/resource-type-filters';
+import { GROUPS_PANEL_ID } from '~/store/groups-panel/groups-panel-actions';
+import { noop } from 'lodash/fp';
+import { GroupResource } from '~/models/group';
+
+export enum ProjectPanelColumnNames {
+ GROUP = "Name",
+ OWNER = "Owner",
+ MEMBERS = "Members",
+}
+
+export const groupsPanelColumns: DataColumns<string> = [
+ {
+ name: ProjectPanelColumnNames.GROUP,
+ selected: true,
+ configurable: true,
+ sortDirection: SortDirection.ASC,
+ filters: createTree(),
+ render: uuid => <ResourceName uuid={uuid} />
+ },
+ {
+ name: ProjectPanelColumnNames.OWNER,
+ selected: true,
+ configurable: true,
+ filters: createTree(),
+ render: uuid => <ResourceOwner uuid={uuid} />,
+ },
+ {
+ name: ProjectPanelColumnNames.MEMBERS,
+ selected: true,
+ configurable: true,
+ filters: createTree(),
+ render: uuid => <span>0</span>,
+ },
+];
+
+export class GroupsPanel extends React.Component {
+
+ render() {
+ return (
+ <DataExplorer
+ id={GROUPS_PANEL_ID}
+ onRowClick={noop}
+ onRowDoubleClick={noop}
+ onContextMenu={noop}
+ contextMenuColumn={true} />
+ );
+ }
+}
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index f4124e2..468797e 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -75,6 +75,7 @@ import { UserPanel } from '~/views/user-panel/user-panel';
import { UserAttributesDialog } from '~/views-components/user-dialog/attributes-dialog';
import { CreateUserDialog } from '~/views-components/dialog-forms/create-user-dialog';
import { HelpApiClientAuthorizationDialog } from '~/views-components/api-client-authorizations-dialog/help-dialog';
+import { GroupsPanel } from '~/views/groups-panel/groups-panel';
type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
@@ -152,7 +153,7 @@ export const WorkbenchPanel =
<Route path={Routes.COMPUTE_NODES} component={ComputeNodePanel} />
<Route path={Routes.API_CLIENT_AUTHORIZATIONS} component={ApiClientAuthorizationPanel} />
<Route path={Routes.MY_ACCOUNT} component={MyAccountPanel} />
- <Route path={Routes.GROUPS} component={() => <h1>Groups panel</h1>} />
+ <Route path={Routes.GROUPS} component={GroupsPanel} />
</Switch>
</Grid>
</Grid>
commit 9c2991d118f83341e64bc413b48a235fa80f38e2
Author: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
Date: Mon Dec 10 16:43:39 2018 +0100
Add possibility to pass the null value to addEqual condition in FilterBuilder
Feature #14505
Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
diff --git a/src/services/api/filter-builder.ts b/src/services/api/filter-builder.ts
index 08746c8..4b3db9f 100644
--- a/src/services/api/filter-builder.ts
+++ b/src/services/api/filter-builder.ts
@@ -11,8 +11,8 @@ export function joinFilters(filters0?: string, filters1?: string) {
export class FilterBuilder {
constructor(private filters = "") { }
- public addEqual(field: string, value?: string | boolean, resourcePrefix?: string) {
- return this.addCondition(field, "=", value, "", "", resourcePrefix );
+ public addEqual(field: string, value?: string | boolean | null, resourcePrefix?: string) {
+ return this.addCondition(field, "=", value, "", "", resourcePrefix);
}
public addLike(field: string, value?: string, resourcePrefix?: string) {
@@ -59,13 +59,13 @@ export class FilterBuilder {
return this.filters;
}
- private addCondition(field: string, cond: string, value?: string | string[] | boolean, prefix: string = "", postfix: string = "", resourcePrefix?: string) {
- if (value) {
+ private addCondition(field: string, cond: string, value?: string | string[] | boolean | null, prefix: string = "", postfix: string = "", resourcePrefix?: string) {
+ if (value !== undefined) {
if (typeof value === "string") {
value = `"${prefix}${value}${postfix}"`;
} else if (Array.isArray(value)) {
value = `["${value.join(`","`)}"]`;
- } else {
+ } else if (value !== null) {
value = value ? "true" : "false";
}
commit 6c024ce6843e5ad5391fc384792480b751e0c663
Author: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
Date: Mon Dec 10 15:16:49 2018 +0100
Setup navigation and create slot for groups panel
Feature #14505
Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
diff --git a/src/routes/route-change-handlers.ts b/src/routes/route-change-handlers.ts
index e2454d6..cbcc065 100644
--- a/src/routes/route-change-handlers.ts
+++ b/src/routes/route-change-handlers.ts
@@ -34,6 +34,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
const apiClientAuthorizationsMatch = Routes.matchApiClientAuthorizationsRoute(pathname);
const myAccountMatch = Routes.matchMyAccountRoute(pathname);
const userMatch = Routes.matchUsersRoute(pathname);
+ const groupsMatch = Routes.matchGroupsRoute(pathname);
if (projectMatch) {
store.dispatch(WorkbenchActions.loadProject(projectMatch.params.id));
@@ -73,5 +74,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
store.dispatch(WorkbenchActions.loadMyAccount);
}else if (userMatch) {
store.dispatch(WorkbenchActions.loadUsers);
+ } else if (groupsMatch) {
+ store.dispatch(WorkbenchActions.loadGroupsPanel);
}
};
diff --git a/src/routes/routes.ts b/src/routes/routes.ts
index 88dfd46..2c6dbea 100644
--- a/src/routes/routes.ts
+++ b/src/routes/routes.ts
@@ -27,7 +27,8 @@ export const Routes = {
KEEP_SERVICES: `/keep-services`,
COMPUTE_NODES: `/nodes`,
USERS: '/users',
- API_CLIENT_AUTHORIZATIONS: `/api_client_authorizations`
+ API_CLIENT_AUTHORIZATIONS: `/api_client_authorizations`,
+ GROUPS: '/groups',
};
export const getResourceUrl = (uuid: string) => {
@@ -108,3 +109,6 @@ export const matchComputeNodesRoute = (route: string) =>
export const matchApiClientAuthorizationsRoute = (route: string) =>
matchPath(route, { path: Routes.API_CLIENT_AUTHORIZATIONS });
+
+export const matchGroupsRoute = (route: string) =>
+ matchPath(route, { path: Routes.GROUPS });
diff --git a/src/store/navigation/navigation-action.ts b/src/store/navigation/navigation-action.ts
index 8d68a4b..8fef0ea 100644
--- a/src/store/navigation/navigation-action.ts
+++ b/src/store/navigation/navigation-action.ts
@@ -77,3 +77,5 @@ export const navigateToComputeNodes = push(Routes.COMPUTE_NODES);
export const navigateToUsers = push(Routes.USERS);
export const navigateToApiClientAuthorizations = push(Routes.API_CLIENT_AUTHORIZATIONS);
+
+export const navigateToGroups = push(Routes.GROUPS);
diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts
index bc5eac6..e0195f9 100644
--- a/src/store/workbench/workbench-actions.ts
+++ b/src/store/workbench/workbench-actions.ts
@@ -445,6 +445,11 @@ export const loadApiClientAuthorizations = handleFirstTimeLoad(
await dispatch(loadApiClientAuthorizationsPanel());
});
+export const loadGroupsPanel = handleFirstTimeLoad(
+ (dispatch: Dispatch<any>) => {
+ dispatch(setBreadcrumbs([{ label: 'Groups' }]));
+ });
+
const finishLoadingProject = (project: GroupContentsResource | string) =>
async (dispatch: Dispatch<any>) => {
const uuid = typeof project === 'string' ? project : project.uuid;
diff --git a/src/views-components/main-app-bar/account-menu.tsx b/src/views-components/main-app-bar/account-menu.tsx
index 44b113d..cb8fd56 100644
--- a/src/views-components/main-app-bar/account-menu.tsx
+++ b/src/views-components/main-app-bar/account-menu.tsx
@@ -14,7 +14,7 @@ import { openCurrentTokenDialog } from '~/store/current-token-dialog/current-tok
import { openRepositoriesPanel } from "~/store/repositories/repositories-actions";
import {
navigateToSshKeys, navigateToKeepServices, navigateToComputeNodes,
- navigateToApiClientAuthorizations, navigateToMyAccount
+ navigateToApiClientAuthorizations, navigateToMyAccount, navigateToGroups
} from '~/store/navigation/navigation-action';
import { openVirtualMachines } from "~/store/virtual-machines/virtual-machines-actions";
import { navigateToUsers } from '~/store/navigation/navigation-action';
@@ -42,6 +42,7 @@ export const AccountMenu = connect(mapStateToProps)(
<MenuItem onClick={() => dispatch(openCurrentTokenDialog)}>Current token</MenuItem>
<MenuItem onClick={() => dispatch(navigateToSshKeys)}>Ssh Keys</MenuItem>
<MenuItem onClick={() => dispatch(navigateToUsers)}>Users</MenuItem>
+ { user.isAdmin && <MenuItem onClick={() => dispatch(navigateToGroups)}>Groups</MenuItem> }
{ user.isAdmin && <MenuItem onClick={() => dispatch(navigateToApiClientAuthorizations)}>Api Tokens</MenuItem> }
{ user.isAdmin && <MenuItem onClick={() => dispatch(navigateToKeepServices)}>Keep Services</MenuItem> }
{ user.isAdmin && <MenuItem onClick={() => dispatch(navigateToComputeNodes)}>Compute Nodes</MenuItem> }
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index 70f2a2d..f4124e2 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -152,6 +152,7 @@ export const WorkbenchPanel =
<Route path={Routes.COMPUTE_NODES} component={ComputeNodePanel} />
<Route path={Routes.API_CLIENT_AUTHORIZATIONS} component={ApiClientAuthorizationPanel} />
<Route path={Routes.MY_ACCOUNT} component={MyAccountPanel} />
+ <Route path={Routes.GROUPS} component={() => <h1>Groups panel</h1>} />
</Switch>
</Grid>
</Grid>
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list