import { mount, configure } from "enzyme";
import * as Adapter from "enzyme-adapter-react-16";
import ColumnSelector, { ColumnSelectorProps, ColumnSelectorTrigger } from "./column-selector";
-import { DataColumn } from "../data-column";
+import { DataColumn } from "../data-table/data-column";
import { ListItem, Checkbox } from "@material-ui/core";
configure({ adapter: new Adapter() });
columnsConfigurator.find(ListItem).simulate("click");
expect(onColumnToggle).toHaveBeenCalledWith(columns[0]);
});
-});
\ No newline at end of file
+});
import * as React from 'react';
import { WithStyles, StyleRulesCallback, Theme, withStyles, IconButton, Paper, List, Checkbox, ListItemText, ListItem } from '@material-ui/core';
import MenuIcon from "@material-ui/icons/Menu";
-import { DataColumn, isColumnConfigurable } from '../data-column';
-import Popover from "../../popover/popover";
+import { DataColumn, isColumnConfigurable } from '../data-table/data-column';
+import Popover from "../popover/popover";
import { IconButtonProps } from '@material-ui/core/IconButton';
export interface ColumnSelectorProps {
import { Table, TableBody, TableRow, TableCell, TableHead, StyleRulesCallback, Theme, WithStyles, withStyles, Typography } from '@material-ui/core';
import { DataColumn } from './data-column';
+export type DataColumns<T> = Array<DataColumn<T>>;
+
export interface DataTableProps<T> {
items: T[];
- columns: Array<DataColumn<T>>;
+ columns: DataColumns<T>;
onItemClick?: (item: T) => void;
}
</TableRow>
)}
</TableBody>
- </Table> : <Typography
+ </Table> : <Typography
className={classes.noItemsInfo}
variant="body2"
gutterBottom>
+++ /dev/null
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-export * from "./data-column";
-export * from "./column-selector/column-selector";
-export { default as ColumnSelector } from "./column-selector/column-selector";
-export * from "./data-table";
-export { default as DataTable } from "./data-table";
\ No newline at end of file
import * as ReactDOM from 'react-dom';
import { Provider } from "react-redux";
import Workbench from './views/workbench/workbench';
-import ProjectList from './components/project-list/project-list';
+import ProjectList from './views-components/project-list/project-list';
import './index.css';
import { Route } from "react-router";
import createBrowserHistory from "history/createBrowserHistory";
import configureStore from "./store/store";
import { ConnectedRouter } from "react-router-redux";
-import ApiToken from "./components/api-token/api-token";
+import ApiToken from "./views-components/api-token/api-token";
import authActions from "./store/auth/auth-action";
import { authService, projectService } from "./services/services";
//
// SPDX-License-Identifier: AGPL-3.0
-export { default as DataExplorer } from "./data-explorer";
-export * from "./data-item";
\ No newline at end of file
+import { Resource } from "./resource";
+
+export interface Collection extends Resource {
+}
//
// SPDX-License-Identifier: AGPL-3.0
-export interface Project {
- name: string;
- createdAt: string;
- modifiedAt: string;
- uuid: string;
- ownerUuid: string;
- href: string;
- kind: string;
+import { Resource } from "./resource";
+
+export interface Project extends Resource {
}
--- /dev/null
+export interface Resource {
+ name: string;
+ createdAt: string;
+ modifiedAt: string;
+ uuid: string;
+ ownerUuid: string;
+ href: string;
+ kind: string;
+}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { serverApi } from "../../common/api/server-api";
+import { Dispatch } from "redux";
+import actions from "../../store/collection/collection-action";
+import UrlBuilder from "../../common/api/url-builder";
+import FilterBuilder, { FilterField } from "../../common/api/filter-builder";
+import { ArvadosResource } from "../response";
+import { Collection } from "../../models/collection";
+
+interface CollectionResource extends ArvadosResource {
+ name: string;
+ description: string;
+ properties: any;
+ portable_data_hash: string;
+ manifest_text: string;
+ replication_desired: number;
+ replication_confirmed: number;
+ replication_confirmed_at: string;
+ trash_at: string;
+ delete_at: string;
+ is_trashed: boolean;
+}
+
+interface CollectionsResponse {
+ offset: number;
+ limit: number;
+ items: CollectionResource[];
+}
+
+export default class CollectionService {
+ public getCollectionList = (parentUuid?: string) => (dispatch: Dispatch): Promise<Collection[]> => {
+ dispatch(actions.COLLECTIONS_REQUEST());
+ if (parentUuid) {
+ const fb = new FilterBuilder();
+ fb.addLike(FilterField.OWNER_UUID, parentUuid);
+ return serverApi.get<CollectionsResponse>('/collections', { params: {
+ filters: fb.get()
+ }}).then(resp => {
+ const collections = resp.data.items.map(g => ({
+ name: g.name,
+ createdAt: g.created_at,
+ modifiedAt: g.modified_at,
+ href: g.href,
+ uuid: g.uuid,
+ ownerUuid: g.owner_uuid,
+ kind: g.kind
+ } as Collection));
+ dispatch(actions.COLLECTIONS_SUCCESS({collections}));
+ return collections;
+ });
+ } else {
+ dispatch(actions.COLLECTIONS_SUCCESS({collections: []}));
+ return Promise.resolve([]);
+ }
+ }
+}
import { Project } from "../../models/project";
import UrlBuilder from "../../common/api/url-builder";
import FilterBuilder, { FilterField } from "../../common/api/filter-builder";
+import { ArvadosResource } from "../response";
+
+interface GroupResource extends ArvadosResource {
+ name: string;
+ group_class: string;
+ description: string;
+ writable_by: string[];
+ delete_at: string;
+ trash_at: string;
+ is_trashed: boolean;
+}
interface GroupsResponse {
offset: number;
limit: number;
- items: Array<{
- href: string;
- kind: string;
- etag: string;
- uuid: string;
- owner_uuid: string;
- created_at: string;
- modified_by_client_uuid: string;
- modified_by_user_uuid: string;
- modified_at: string;
- name: string;
- group_class: string;
- description: string;
- writable_by: string[];
- delete_at: string;
- trash_at: string;
- is_trashed: boolean;
- }>;
+ items: GroupResource[];
}
export default class ProjectService {
fb.addLike(FilterField.OWNER_UUID, parentUuid);
return serverApi.get<GroupsResponse>('/groups', { params: {
filters: fb.get()
- }}).then(groups => {
- const projects = groups.data.items.map(g => ({
+ }}).then(resp => {
+ const projects = resp.data.items.map(g => ({
name: g.name,
createdAt: g.created_at,
modifiedAt: g.modified_at,
--- /dev/null
+export interface ArvadosResource {
+ uuid: string;
+ owner_uuid: string;
+ created_at: string;
+ modified_by_client_uuid: string;
+ modified_by_user_uuid: string;
+ modified_at: string;
+ href: string;
+ kind: string;
+ etag: string;
+}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Collection } from "../../models/collection";
+import { default as unionize, ofType, UnionOf } from "unionize";
+
+const actions = unionize({
+ CREATE_COLLECTION: ofType<Collection>(),
+ REMOVE_COLLECTION: ofType<string>(),
+ COLLECTIONS_REQUEST: ofType<any>(),
+ COLLECTIONS_SUCCESS: ofType<{ collections: Collection[] }>(),
+}, {
+ tag: 'type',
+ value: 'payload'
+});
+
+export type CollectionAction = UnionOf<typeof actions>;
+export default actions;
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import collectionsReducer from "./collection-reducer";
+import actions from "./collection-action";
+
+describe('collection-reducer', () => {
+ it('should add new collection to the list', () => {
+ const initialState = undefined;
+ const collection = {
+ name: 'test',
+ href: 'href',
+ createdAt: '2018-01-01',
+ modifiedAt: '2018-01-01',
+ ownerUuid: 'owner-test123',
+ uuid: 'test123',
+ kind: ""
+ };
+
+ const state = collectionsReducer(initialState, actions.CREATE_COLLECTION(collection));
+ expect(state).toEqual([collection]);
+ });
+
+ it('should load collections', () => {
+ const initialState = undefined;
+ const collection = {
+ name: 'test',
+ href: 'href',
+ createdAt: '2018-01-01',
+ modifiedAt: '2018-01-01',
+ ownerUuid: 'owner-test123',
+ uuid: 'test123',
+ kind: ""
+ };
+
+ const collections = [collection, collection];
+ const state = collectionsReducer(initialState, actions.COLLECTIONS_SUCCESS({ collections }));
+ expect(state).toEqual([{
+ active: false,
+ open: false,
+ id: "test123",
+ items: [],
+ data: collection,
+ status: 0
+ }, {
+ active: false,
+ open: false,
+ id: "test123",
+ items: [],
+ data: collection,
+ status: 0
+ }
+ ]);
+ });
+});
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import actions, { CollectionAction } from "./collection-action";
+import { Collection } from "../../models/collection";
+
+export type CollectionState = Collection[];
+
+
+const collectionsReducer = (state: CollectionState = [], action: CollectionAction) => {
+ return actions.match(action, {
+ CREATE_COLLECTION: collection => [...state, collection],
+ REMOVE_COLLECTION: () => state,
+ COLLECTIONS_REQUEST: () => {
+ return state;
+ },
+ COLLECTIONS_SUCCESS: ({ collections }) => {
+ return collections;
+ },
+ default: () => state
+ });
+};
+
+export default collectionsReducer;
import { History } from "history";
import projectsReducer, { ProjectState } from "./project/project-reducer";
import authReducer, { AuthState } from "./auth/auth-reducer";
+import collectionsReducer from "./collection/collection-reducer";
const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
const rootReducer = combineReducers({
auth: authReducer,
projects: projectsReducer,
+ collections: collectionsReducer,
router: routerReducer
});
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { DataTable, DataTableProps, DataColumn, ColumnSelector } from "../../components/data-table";
import { Typography, Grid, ListItem, Divider, List, ListItemIcon, ListItemText, Paper, Toolbar } from '@material-ui/core';
import IconButton, { IconButtonProps } from '@material-ui/core/IconButton';
import MoreVertIcon from "@material-ui/icons/MoreVert";
-import Popover from '../popover/popover';
+import Popover from '../../components/popover/popover';
import { formatFileSize, formatDate } from '../../common/formatters';
import { DataItem } from './data-item';
+import { DataColumns, DataTableProps } from "../../components/data-table/data-table";
+import { DataColumn } from "../../components/data-table/data-column";
+import ColumnSelector from "../../components/column-selector/column-selector";
+import DataTable from "../../components/data-table/data-table";
interface DataExplorerProps {
items: DataItem[];
onItemClick: (item: DataItem) => void;
}
-type DataExplorerState = Pick<DataTableProps<DataItem>, "columns">;
+interface DataExplorerState {
+ columns: DataColumns<DataItem>;
+}
class DataExplorer extends React.Component<DataExplorerProps, DataExplorerState> {
state: DataExplorerState = {
// SPDX-License-Identifier: AGPL-3.0
export interface DataItem {
+ uuid: string;
name: string;
type: string;
owner: string;
lastModified: string;
fileSize?: number;
status?: string;
-}
\ No newline at end of file
+}
import { mount, configure, ReactWrapper } from "enzyme";
import * as Adapter from "enzyme-adapter-react-16";
import MainAppBar from "./main-app-bar";
-import SearchBar from "./search-bar/search-bar";
-import Breadcrumbs from "../breadcrumbs/breadcrumbs";
-import DropdownMenu from "./dropdown-menu/dropdown-menu";
+import SearchBar from "../../components/search-bar/search-bar";
+import Breadcrumbs from "../../components/breadcrumbs/breadcrumbs";
+import DropdownMenu from "../../components/dropdown-menu/dropdown-menu";
import { Button, MenuItem, IconButton } from "@material-ui/core";
import { User } from "../../models/user";
mainAppBar.find(DropdownMenu).at(0).find(MenuItem).at(1).simulate("click");
expect(onMenuItemClick).toBeCalledWith(menuItems.accountMenu[0]);
});
-});
\ No newline at end of file
+});
import NotificationsIcon from "@material-ui/icons/Notifications";
import PersonIcon from "@material-ui/icons/Person";
import HelpIcon from "@material-ui/icons/Help";
-import SearchBar from "./search-bar/search-bar";
-import Breadcrumbs, { Breadcrumb } from "../breadcrumbs/breadcrumbs";
-import DropdownMenu from "./dropdown-menu/dropdown-menu";
+import SearchBar from "../../components/search-bar/search-bar";
+import Breadcrumbs, { Breadcrumb } from "../../components/breadcrumbs/breadcrumbs";
+import DropdownMenu from "../../components/dropdown-menu/dropdown-menu";
import { User, getUserFullname } from "../../models/user";
export interface MainAppBarMenuItem {
}
});
-export default withStyles(styles)(MainAppBar);
\ No newline at end of file
+export default withStyles(styles)(MainAppBar);
import CircularProgress from '@material-ui/core/CircularProgress';
import ProjectTree from './project-tree';
-import { TreeItem } from '../tree/tree';
+import { TreeItem } from '../../components/tree/tree';
import { Project } from '../../models/project';
Enzyme.configure({ adapter: new Adapter() });
import ListItemIcon from '@material-ui/core/ListItemIcon';
import Typography from '@material-ui/core/Typography';
-import Tree, { TreeItem, TreeItemStatus } from '../tree/tree';
+import Tree, { TreeItem, TreeItemStatus } from '../../components/tree/tree';
import { Project } from '../../models/project';
type CssRules = 'active' | 'listItemText' | 'row' | 'treeContainer';
--- /dev/null
+import { TreeItem } from "../../components/tree/tree";
+import { Project } from "../../models/project";
+import { DataItem } from "../../views-components/data-explorer/data-item";
+
+export const mapProjectTreeItem = (item: TreeItem<Project>): DataItem => ({
+ name: item.data.name,
+ type: item.data.kind,
+ owner: item.data.ownerUuid,
+ lastModified: item.data.modifiedAt,
+ uuid: item.data.uuid
+});
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { DataTableProps } from "../../components/data-table";
import { RouteComponentProps } from 'react-router';
import { Project } from '../../models/project';
import { ProjectState, findTreeItem } from '../../store/project/project-reducer';
import { connect, DispatchProp } from 'react-redux';
import { push } from 'react-router-redux';
import projectActions from "../../store/project/project-action";
-import { DataExplorer, DataItem } from '../../components/data-explorer';
-import { TreeItem } from '../../components/tree/tree';
+import { DataColumns } from "../../components/data-table/data-table";
+import { DataItem } from "../../views-components/data-explorer/data-item";
+import DataExplorer from "../../views-components/data-explorer/data-explorer";
+import { mapProjectTreeItem } from "../../views-selectors/data-explorer/data-explorer";
interface DataExplorerViewDataProps {
projects: ProjectState;
}
type DataExplorerViewProps = DataExplorerViewDataProps & RouteComponentProps<{ name: string }> & DispatchProp;
-
-type DataExplorerViewState = Pick<DataTableProps<Project>, "columns">;
-
-interface MappedProjectItem extends DataItem {
- uuid: string;
-}
+type DataExplorerViewState = DataColumns<Project>;
class DataExplorerView extends React.Component<DataExplorerViewProps, DataExplorerViewState> {
const projectItems = project && project.items || [];
return (
<DataExplorer
- items={projectItems.map(mapTreeItem)}
+ items={projectItems.map(mapProjectTreeItem)}
onItemClick={this.goToProject}
/>
);
}
- goToProject = (project: MappedProjectItem) => {
- this.props.dispatch(push(`/project/${project.uuid}`));
- this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(project.uuid));
+ goToProject = (item: DataItem) => {
+ this.props.dispatch(push(`/project/${item.uuid}`));
+ this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(item.uuid));
}
-
}
-const mapTreeItem = (item: TreeItem<Project>): MappedProjectItem => ({
- name: item.data.name,
- type: item.data.kind,
- owner: item.data.ownerUuid,
- lastModified: item.data.modifiedAt,
- uuid: item.data.uuid
-});
-
-
export default connect(
(state: RootState) => ({
projects: state.projects
import authActions from "../../store/auth/auth-action";
import { User } from "../../models/user";
import { RootState } from "../../store/store";
-import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItem } from '../../components/main-app-bar/main-app-bar';
+import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItem } from '../../views-components/main-app-bar/main-app-bar';
import { Breadcrumb } from '../../components/breadcrumbs/breadcrumbs';
import { push } from 'react-router-redux';
import projectActions from "../../store/project/project-action";
-import ProjectTree from '../../components/project-tree/project-tree';
+import ProjectTree from '../../views-components/project-tree/project-tree';
import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
import { Project } from "../../models/project";
import { projectService } from '../../services/services';
if (status === TreeItemStatus.Loaded) {
this.openProjectItem(itemId);
} else {
- this.props.dispatch<any>(projectService.getProjectList(itemId)).then(() => this.openProjectItem(itemId));
+ this.props.dispatch<any>(projectService.getProjectList(itemId))
+ .then(() => this.openProjectItem(itemId));
}
}