RUN cd /usr/src/arvados && \
apt-get update && \
go mod download && \
- go run ./cmd/arvados-server install -type test && cd .. && \
+ go run ./cmd/arvados-server install -type test && \
+ # Installing WB2 deps persists cypress to the home folder
+ # This is convenient to running cypress locally where yarn is run outside the container
+ cd services/workbench2 && \
+ yarn install && \
+ cd /usr/src/arvados && \
rm -rf arvados && \
apt-get clean
CANCELLING = 'Cancelling',
}
+/**
+ * Gets a process from the store using container request uuid
+ * @param uuid container request associated with process
+ * @returns a Process object with containerRequest and optional container or undefined
+ */
export const getProcess = (uuid: string) => (resources: ResourcesState): Process | undefined => {
if (extractUuidKind(uuid) === ResourceKind.CONTAINER_REQUEST) {
const containerRequest = getResource<ContainerRequestResource>(uuid)(resources);
dispatch(treePickerActions.EXPAND_TREE_PICKER_NODE({ id, pickerId }));
};
+export const extractGroupContentsNodeData = (expandableCollections: boolean) => (item: GroupContentsResource) => (
+ item.uuid === "more-items-available"
+ ? {
+ id: item.uuid,
+ value: item,
+ status: TreeNodeStatus.LOADED
+ }
+ : {
+ id: item.uuid,
+ value: item,
+ status: item.kind === ResourceKind.PROJECT
+ ? TreeNodeStatus.INITIAL
+ : item.kind === ResourceKind.COLLECTION && expandableCollections
+ ? TreeNodeStatus.INITIAL
+ : TreeNodeStatus.LOADED
+ }
+);
interface LoadProjectParamsWithId extends LoadProjectParams {
id: string;
pickerId: string;
return true;
}),
- extractNodeData: item => (
- item.uuid === "more-items-available" ?
- {
- id: item.uuid,
- value: item,
- status: TreeNodeStatus.LOADED
- }
- : {
- id: item.uuid,
- value: item,
- status: item.kind === ResourceKind.PROJECT
- ? TreeNodeStatus.INITIAL
- : includeDirectories || includeFiles
- ? TreeNodeStatus.INITIAL
- : TreeNodeStatus.LOADED
- }),
+ extractNodeData: extractGroupContentsNodeData(includeDirectories || includeFiles),
}));
} catch(e) {
console.error("Failed to load project into tree picker:", e);;
return true;
}),
- extractNodeData: item => ({
- id: item.uuid,
- value: item,
- status: item.kind === ResourceKind.PROJECT
- ? TreeNodeStatus.INITIAL
- : includeDirectories || includeFiles
- ? TreeNodeStatus.INITIAL
- : TreeNodeStatus.LOADED
- }),
+ extractNodeData: extractGroupContentsNodeData(includeDirectories || includeFiles),
}));
}
};
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from "react";
+import Axios from "axios";
+import Adapter from "enzyme-adapter-react-16";
+import { configure, mount } from "enzyme";
+import { mockConfig } from "common/config";
+import { ServiceRepository, createServices } from "services/services";
+import { createBrowserHistory } from "history";
+import { ApiActions } from "services/api/api-actions";
+import { Provider } from "react-redux";
+import { configureStore } from "store/store";
+import { TreePicker } from "./tree-picker";
+import { initUserProject, receiveTreePickerData, extractGroupContentsNodeData } from "store/tree-picker/tree-picker-actions";
+import { authActions } from "store/auth/auth-action";
+import { ResourceKind } from "models/resource";
+import { updateResources } from "store/resources/resources-actions";
+
+configure({ adapter: new Adapter() });
+
+describe('<TreePicker />', () => {
+ let store;
+ let services: ServiceRepository;
+ const axiosInst = Axios.create({ headers: {} });
+ const config: any = {};
+ const actions: ApiActions = {
+ progressFn: (id: string, working: boolean) => { },
+ errorFn: (id: string, message: string) => { }
+ };
+ const TEST_PICKER_ID = 'testPickerId';
+ const fakeUser = {
+ email: "test@test.com",
+ firstName: "John",
+ lastName: "Doe",
+ uuid: "zzzzz-tpzed-xurymjxw79nv3jz",
+ ownerUuid: "ownerUuid",
+ username: "username",
+ prefs: {},
+ isAdmin: false,
+ isActive: true,
+ canWrite: false,
+ canManage: false,
+ };
+ const renderItem = (item) => (
+ <li data-id={item.id}>{item.data.name}</li>
+ );
+
+ beforeEach(() => {
+ services = createServices(mockConfig({}), actions, axiosInst);
+ store = configureStore(createBrowserHistory(), services, config);
+ store.dispatch(authActions.USER_DETAILS_SUCCESS(fakeUser));
+ store.dispatch(initUserProject(TEST_PICKER_ID));
+ });
+
+ it("renders tree picker with initial home project state", () => {
+ let treePicker = mount(
+ <Provider store={store}>
+ <TreePicker
+ pickerId={TEST_PICKER_ID}
+ render={renderItem}
+ onContextMenu={() => {}}
+ toggleItemOpen={() => {}}
+ toggleItemActive={() => {}}
+ toggleItemSelection={() => {}}
+ />
+ </Provider>);
+
+ expect(treePicker.find(`li[data-id="${fakeUser.uuid}"]`).text()).toBe('Home Projects');
+ });
+
+ it("displays item loaded into treePicker store", () => {
+ const fakeProject = {
+ uuid: "zzzzz-j7d0g-111111111111111",
+ name: "FakeProject",
+ kind: ResourceKind.PROJECT,
+ };
+
+ store.dispatch(receiveTreePickerData({
+ id: fakeUser.uuid,
+ pickerId: TEST_PICKER_ID,
+ data: [fakeProject],
+ extractNodeData: extractGroupContentsNodeData(false)
+ }));
+
+ let treePicker = mount(
+ <Provider store={store}>
+ <TreePicker
+ pickerId={TEST_PICKER_ID}
+ render={renderItem}
+ onContextMenu={() => {}}
+ toggleItemOpen={() => {}}
+ toggleItemActive={() => {}}
+ toggleItemSelection={() => {}}
+ />
+ </Provider>);
+
+ expect(treePicker.find(`[data-id="${fakeUser.uuid}"]`).text()).toBe('Home Projects');
+ expect(treePicker.find(`[data-id="${fakeProject.uuid}"]`).text()).toBe('FakeProject');
+ });
+
+ it("preserves treenode name when exists in resources", () => {
+ const treeProjectResource = {
+ uuid: "zzzzz-j7d0g-111111111111111",
+ name: "FakeProject",
+ kind: ResourceKind.PROJECT,
+ };
+ const treeProjectResource2 = {
+ uuid: "zzzzz-j7d0g-222222222222222",
+ name: "",
+ kind: ResourceKind.PROJECT,
+ };
+
+ const storeProjectResource = {
+ ...treeProjectResource,
+ name: "StoreProjectName",
+ description: "Test description",
+ };
+ const storeProjectResource2 = {
+ ...treeProjectResource2,
+ name: "StoreProjectName2",
+ description: "Test description",
+ };
+
+ store.dispatch(updateResources([storeProjectResource, storeProjectResource2]));
+ store.dispatch(receiveTreePickerData({
+ id: fakeUser.uuid,
+ pickerId: TEST_PICKER_ID,
+ data: [treeProjectResource, treeProjectResource2],
+ extractNodeData: extractGroupContentsNodeData(false)
+ }));
+
+ let treePicker = mount(
+ <Provider store={store}>
+ <TreePicker
+ pickerId={TEST_PICKER_ID}
+ render={renderItem}
+ onContextMenu={() => {}}
+ toggleItemOpen={() => {}}
+ toggleItemActive={() => {}}
+ toggleItemSelection={() => {}}
+ />
+ </Provider>);
+
+ expect(treePicker.find(`[data-id="${fakeUser.uuid}"]`).text()).toBe('Home Projects');
+ expect(treePicker.find(`[data-id="${treeProjectResource.uuid}"]`).text()).toBe('FakeProject');
+ expect(treePicker.find(`[data-id="${treeProjectResource2.uuid}"]`).text()).toBe('');
+ });
+
+});
// SPDX-License-Identifier: AGPL-3.0
import { connect } from "react-redux";
-import { Tree, TreeProps, TreeItem, TreeItemStatus } from "components/tree/tree";
+import { Tree as TreeComponent, TreeProps, TreeItem, TreeItemStatus } from "components/tree/tree";
import { RootState } from "store/store";
-import { getNodeChildrenIds, Tree as Ttree, createTree, getNode, TreeNodeStatus } from 'models/tree';
+import { getNodeChildrenIds, Tree, createTree, getNode, TreeNodeStatus } from 'models/tree';
import { Dispatch } from "redux";
import { initTreeNode } from '../../models/tree';
import { ResourcesState } from "store/resources/resources";
+import { Resource } from "models/resource";
type Callback<T> = (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>, pickerId: string) => void;
export interface TreePickerProps<T> {
toggleItemSelection: Callback<T>;
}
-const flatTree = (itemsIdMap: Map<string, any>, depth: number, items?: any): [] => {
+const flatTree = <T>(itemsIdMap: Map<string, TreeItem<T>>, depth: number, items?: TreeItem<T>[]): TreeItem<T>[] => {
return items ? items
- .map((item: any) => addToItemsIdMap(item, itemsIdMap))
- .reduce((prev: Array<any>, next: any) => {
+ .map((item: TreeItem<T>) => addToItemsIdMap(item, itemsIdMap))
+ .reduce((acc: Array<TreeItem<T>>, next: TreeItem<T>) => {
const { items } = next;
- prev.push({ ...next, depth });
- prev.push(...(next.open ? flatTree(itemsIdMap, depth + 1, items) : []));
- return prev;
- }, []) : [];
+ acc.push({ ...next, depth });
+ acc.push(...(next.open ? flatTree(itemsIdMap, depth + 1, items) : []));
+ return acc;
+ }, [] as TreeItem<T>[]) : [];
};
-const addToItemsIdMap = <T>(item: TreeItem<T>, itemsIdMap: Map<string, TreeItem<T>>) => {
+const addToItemsIdMap = <T>(item: TreeItem<T>, itemsIdMap: Map<string, TreeItem<T>>): TreeItem<T> => {
itemsIdMap[item.id] = item;
return item;
};
const mapStateToProps =
<T>(state: RootState, props: TreePickerProps<T>): Pick<TreeProps<T>, 'items' | 'disableRipple' | 'itemsMap'> => {
const itemsIdMap: Map<string, TreeItem<T>> = new Map();
- const tree = state.treePicker[props.pickerId] || createTree();
+ const tree: Tree<T> = state.treePicker[props.pickerId] || createTree<T>();
return {
disableRipple: true,
items: getNodeChildrenIds('')(tree)
};
};
-const mapDispatchToProps = (_: Dispatch, props: TreePickerProps<any>): Pick<TreeProps<any>, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive' | 'toggleItemSelection'> => ({
+const mapDispatchToProps = <T>(_: Dispatch, props: TreePickerProps<T>): Pick<TreeProps<T>, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive' | 'toggleItemSelection'> => ({
onContextMenu: (event, item) => props.onContextMenu(event, item, props.pickerId),
toggleItemActive: (event, item) => props.toggleItemActive(event, item, props.pickerId),
toggleItemOpen: (event, item) => props.toggleItemOpen(event, item, props.pickerId),
toggleItemSelection: (event, item) => props.toggleItemSelection(event, item, props.pickerId),
});
-export const TreePicker = connect(mapStateToProps, mapDispatchToProps)(Tree);
+export const TreePicker = connect(mapStateToProps, mapDispatchToProps)(TreeComponent);
-const treePickerToTreeItems = (tree: Ttree<any>, resources: ResourcesState) =>
+const treePickerToTreeItems = <T>(tree: Tree<T>, resources: ResourcesState) =>
(id: string): TreeItem<any> => {
const node = getNode(id)(tree) || initTreeNode({ id: '', value: 'InvalidNode' });
const items = getNodeChildrenIds(node.id)(tree)
.map(treePickerToTreeItems(tree, resources));
- const resource = resources[node.id];
+ const resource = resources[node.id] as (Resource | undefined);
+
return {
active: node.active,
- data: resource ? { ...resource, name: node.value.name || node.value } : node.value,
+ data: resource
+ ? {
+ ...resource,
+ name: typeof node.value === "string"
+ ? node.value
+ : typeof (node.value as any).name === "string"
+ ? (node.value as any).name
+ : ""
+ }
+ : node.value,
id: node.id,
items: items.length > 0 ? items : undefined,
open: node.expanded,
return TreeItemStatus.LOADED;
}
};
-
case "${o}" in
i)
# Interactive mode
- CYPRESS_MODE="open"
+ CYPRESS_MODE="open --e2e"
;;
a)
ARVADOS_DIR=${OPTARG}