.and(`${isWritable ? '' : 'not.'}contain`, 'Rename')
.and(`${isWritable ? '' : 'not.'}contain`, 'Remove');
cy.get('body').click(); // Collapse the menu
+ // File/dir item 'more options' button
+ cy.get('[data-cy=file-item-options-btn')
+ .first()
+ .click()
+ cy.get('[data-cy=context-menu]')
+ .should(`${isWritable ? '' : 'not.'}contain`, 'Remove');
+ cy.get('body').click(); // Collapse the menu
// Hamburger 'more options' menu button
cy.get('[data-cy=collection-files-panel-options-btn]')
.click()
}
};
Workbench: {
+ DisableSharingURLsUI: boolean;
ArvadosDocsite: string;
FileViewersConfigURL: string;
WelcomePageHTML: string;
WebShell: { ExternalURL: "" },
},
Workbench: {
+ DisableSharingURLsUI: false,
ArvadosDocsite: "",
FileViewersConfigURL: "",
WelcomePageHTML: "",
import { FixedSizeList } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import servicesProvider from 'common/service-provider';
-import { CustomizeTableIcon, DownloadIcon } from 'components/icon/icon';
+import { CustomizeTableIcon, DownloadIcon, MoreOptionsIcon } from 'components/icon/icon';
import { SearchInput } from 'components/search-input/search-input';
import {
ListItemIcon,
| "pathPanelPathWrapper"
| "uploadButton"
| "uploadIcon"
+ | "moreOptionsButton"
+ | "moreOptions"
| "loader"
| "wrapper"
| "dataWrapper"
},
uploadButton: {
float: 'right',
- }
+ },
+ moreOptionsButton: {
+ width: theme.spacing.unit * 3,
+ height: theme.spacing.unit * 3,
+ marginRight: theme.spacing.unit,
+ marginTop: 'auto',
+ marginBottom: 'auto',
+ justifyContent: 'center',
+ },
+ moreOptions: {
+ position: 'absolute'
+ },
});
const pathPromise = {};
const handleClick = React.useCallback(
(event: any) => {
let isCheckbox = false;
+ let isMoreButton = false;
let elem = event.target;
if (elem.type === 'checkbox') {
isCheckbox = true;
}
+ // The "More options" button click event could be triggered on its
+ // internal graphic element.
+ else if ((elem.dataset && elem.dataset.id === 'moreOptions') || (elem.parentNode && elem.parentNode.dataset && elem.parentNode.dataset.id === 'moreOptions')) {
+ isMoreButton = true;
+ }
while (elem && elem.dataset && !elem.dataset.item) {
elem = elem.parentNode;
}
- if (elem && elem.dataset && !isCheckbox) {
+ if (elem && elem.dataset && !isCheckbox && !isMoreButton) {
const { parentPath, subfolderPath, breadcrumbPath, type } = elem.dataset;
if (breadcrumbPath) {
const item = collectionPanelFiles[id];
props.onSelectionToggle(event, item);
}
+ if (isMoreButton) {
+ const { id } = elem.dataset;
+ const item: any = {
+ id,
+ data: rightData.find((elem) => elem.id === id),
+ };
+ onItemMenuOpen(event, item, isWritable);
+ }
},
[path, setPath, collectionPanelFiles] // eslint-disable-line react-hooks/exhaustive-deps
);
marginLeft: 'auto', marginRight: '1rem' }}>
{ formatFileSize(size) }
</span>
+ <Tooltip title="More options" disableFocusListener>
+ <IconButton data-id='moreOptions'
+ data-cy='file-item-options-btn'
+ className={classes.moreOptionsButton}>
+ <MoreOptionsIcon
+ data-id='moreOptions'
+ className={classes.moreOptions} />
+ </IconButton>
+ </Tooltip>
</div>
} }</FixedSizeList>
: <div className={classes.rowEmpty}>This collection is empty</div>
import { ArvadosTheme } from "common/custom-theme";
import { createTree } from 'models/tree';
import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
-import { CloseIcon, MaximizeIcon, MoreOptionsIcon } from 'components/icon/icon';
+import { CloseIcon, IconType, MaximizeIcon, MoreOptionsIcon } from 'components/icon/icon';
import { PaperProps } from '@material-ui/core/Paper';
import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
rowsPerPageOptions: number[];
page: number;
contextMenuColumn: boolean;
- dataTableDefaultView?: React.ReactNode;
+ defaultViewIcon?: IconType;
+ defaultViewMessages?: string[];
working?: boolean;
currentRefresh?: string;
currentRoute?: string;
columns, onContextMenu, onFiltersChange, onSortToggle, extractKey,
rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch,
items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
- dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
+ defaultViewIcon, defaultViewMessages, hideColumnSelector, actions, paperProps, hideSearchInput,
paperKey, fetchMode, currentItemUuid, title,
doHidePanel, doMaximizePanel, panelName, panelMaximized, elementPath
} = this.props;
onSortToggle={onSortToggle}
extractKey={extractKey}
working={this.state.showLoading}
- defaultView={dataTableDefaultView}
+ defaultViewIcon={defaultViewIcon}
+ defaultViewMessages={defaultViewMessages}
currentItemUuid={currentItemUuid}
currentRoute={paperKey} /></Grid>
<Grid item xs><Toolbar className={classes.footer}>
marginBottom: theme.spacing.unit * 4,
},
});
-type DataTableDefaultViewDataProps = Partial<Pick<DefaultViewDataProps, 'icon' | 'messages'>>;
+type DataTableDefaultViewDataProps = Partial<Pick<DefaultViewDataProps, 'icon' | 'messages' | 'filtersApplied'>>;
type DataTableDefaultViewProps = DataTableDefaultViewDataProps & WithStyles<CssRules>;
export const DataTableDefaultView = withStyles(styles)(
({ classes, ...props }: DataTableDefaultViewProps) => {
const icon = props.icon || DetailsIcon;
- const messages = props.messages || ['No items found'];
+ const filterWarning: string[] = props.filtersApplied ? ['Filters are applied to the data.'] : [];
+ const messages = filterWarning.concat(props.messages || ['No items found']);
return <DefaultView {...classes} {...{ icon, messages }} />;
});
//
// SPDX-License-Identifier: AGPL-3.0
-import React from "react";
+import React, { useEffect } from "react";
import {
WithStyles,
withStyles,
import { createTree } from 'models/tree';
import { DataTableFilters, DataTableFiltersTree } from "./data-table-filters-tree";
import { getNodeDescendants } from 'models/tree';
+import debounce from "lodash/debounce";
export type CssRules = "root" | "icon" | "iconButton" | "active" | "checkbox";
open={!!this.state.anchorEl}
anchorOrigin={DefaultTransformOrigin}
transformOrigin={DefaultTransformOrigin}
- onClose={this.cancel}>
+ onClose={this.close}>
<Card>
<CardContent>
<Typography variant="caption">
<DataTableFiltersTree
filters={this.state.filters}
mutuallyExclusive={this.props.mutuallyExclusive}
- onChange={filters => {
- this.setState({ filters });
- if (this.props.mutuallyExclusive) {
- const { onChange } = this.props;
- if (onChange) {
- onChange(filters);
- }
- this.setState({ anchorEl: undefined });
- }
- }} />
+ onChange={this.onChange} />
{this.props.mutuallyExclusive ||
<CardActions>
- <Button
- color="primary"
- variant='contained'
- size="small"
- onClick={this.submit}>
- Ok
- </Button>
<Button
color="primary"
variant="outlined"
size="small"
- onClick={this.cancel}>
- Cancel
+ onClick={this.close}>
+ Close
</Button>
</CardActions >
}
</Card>
</Popover>
+ <this.MountHandler />
</>;
}
this.setState({ anchorEl: this.icon.current || undefined });
}
- submit = () => {
+ onChange = (filters) => {
+ this.setState({ filters });
+ if (this.props.mutuallyExclusive) {
+ // Mutually exclusive filters apply immediately
+ const { onChange } = this.props;
+ if (onChange) {
+ onChange(filters);
+ }
+ this.close();
+ } else {
+ // Non-mutually exclusive filters are debounced
+ this.submit();
+ }
+ }
+
+ submit = debounce (() => {
const { onChange } = this.props;
if (onChange) {
onChange(this.state.filters);
}
- this.setState({ anchorEl: undefined });
- }
+ }, 1000);
+
+ MountHandler = () => {
+ useEffect(() => {
+ return () => {
+ this.submit.cancel();
+ }
+ },[]);
+ return null;
+ };
- cancel = () => {
+ close = () => {
this.setState(prev => ({
...prev,
- filters: prev.prevFilters,
anchorEl: undefined
}));
}
- setFilters = (filters: DataTableFilters) => {
- this.setState({ filters });
- }
-
}
);
levelIndentation={hasSubfilters ? 20 : 0}
itemRightPadding={20}
items={filtersToTree(filters)}
- render={renderItem}
+ render={this.props.mutuallyExclusive ? renderRadioItem : renderItem}
showSelection
useRadioButtons={this.props.mutuallyExclusive}
disableRipple
}
const renderItem = (item: TreeItem<DataTableFilterItem>) =>
- <span>{item.data.name}</span>;
+ <span>
+ {item.data.name}
+ {item.initialState !== item.selected ? <>
+ *
+ </> : null}
+ </span>;
+
+const renderRadioItem = (item: TreeItem<DataTableFilterItem>) =>
+ <span>
+ {item.data.name}
+ </span>;
const filterToTreeItem = (filters: DataTableFilters) =>
(id: string): TreeItem<any> => {
const node = getNode(id)(filters) || initTreeNode({ id: '', value: 'InvalidNode' });
const items = getNodeChildrenIds(node.id)(filters)
.map(filterToTreeItem(filters));
+ const isIndeterminate = !node.selected && items.some(i => i.selected || i.indeterminate);
return {
active: node.active,
items: items.length > 0 ? items : undefined,
open: node.expanded,
selected: node.selected,
+ initialState: node.initialState,
+ indeterminate: isIndeterminate,
status: TreeItemStatus.LOADED,
};
};
import { DataTableDefaultView } from '../data-table-default-view/data-table-default-view';
import { DataTableFilters } from '../data-table-filters/data-table-filters-tree';
import { DataTableFiltersPopover } from '../data-table-filters/data-table-filters-popover';
-import { countNodes } from 'models/tree';
-import { PendingIcon } from 'components/icon/icon';
+import { countNodes, getTreeDirty } from 'models/tree';
+import { IconType, PendingIcon } from 'components/icon/icon';
import { SvgIconProps } from '@material-ui/core/SvgIcon';
import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward';
onFiltersChange: (filters: DataTableFilters, column: DataColumn<T>) => void;
extractKey?: (item: T) => React.Key;
working?: boolean;
- defaultView?: React.ReactNode;
+ defaultViewIcon?: IconType;
+ defaultViewMessages?: string[];
currentItemUuid?: string;
currentRoute?: string;
}
icon={PendingIcon}
messages={['Loading data, please wait.']} />
</div> }
- {items.length === 0 && !working && this.renderNoItemsPlaceholder()}
+ {items.length === 0 && !working && this.renderNoItemsPlaceholder(this.props.columns)}
</div>
</div>;
}
- renderNoItemsPlaceholder = () => {
- return this.props.defaultView
- ? this.props.defaultView
- : <DataTableDefaultView />;
+ renderNoItemsPlaceholder = (columns: DataColumns<T>) => {
+ const dirty = columns.some((column) => getTreeDirty('')(column.filters));
+ return <DataTableDefaultView
+ icon={this.props.defaultViewIcon}
+ messages={this.props.defaultViewMessages}
+ filtersApplied={dirty} />;
}
renderHeadCell = (column: DataColumn<T>, index: number) => {
export interface DefaultViewDataProps {
classRoot?: string;
messages: string[];
+ filtersApplied?: boolean;
classMessage?: string;
icon: IconType;
classIcon?: string;
className={classnames([classes.message, classMessage])}>{msg}</Typography>;
})}
</Typography>
-);
\ No newline at end of file
+);
import { connect } from "react-redux";
import { RootState } from "store/store";
-export interface FileThumbnailProps {
+interface FileThumbnailProps {
file: FileTreeData;
}
//
// SPDX-License-Identifier: AGPL-3.0
-import React from "react";
-import { TreeItem } from "../tree/tree";
-import { DirectoryIcon, MoreOptionsIcon, DefaultIcon, FileIcon } from "../icon/icon";
-import { Typography, IconButton, StyleRulesCallback, withStyles, WithStyles, Tooltip } from '@material-ui/core';
-import { formatFileSize } from "common/formatters";
-import { ListItemTextIcon } from "../list-item-text-icon/list-item-text-icon";
-import { FileTreeData } from "./file-tree-data";
-
-type CssRules = "root" | "spacer" | "sizeInfo" | "button" | "moreOptions";
-
-const fileTreeItemStyle: StyleRulesCallback<CssRules> = theme => ({
- root: {
- display: "flex",
- alignItems: "center",
- paddingRight: `${theme.spacing.unit * 1.5}px`
- },
- spacer: {
- flex: "1"
- },
- sizeInfo: {
- width: `${theme.spacing.unit * 8}px`
- },
- button: {
- width: theme.spacing.unit * 3,
- height: theme.spacing.unit * 3,
- marginRight: theme.spacing.unit,
- },
- moreOptions: {
- position: 'absolute'
- }
-});
-
-export interface FileTreeItemProps {
- item: TreeItem<FileTreeData>;
- onMoreClick: (event: React.MouseEvent<any>, item: TreeItem<FileTreeData>) => void;
-}
-export const FileTreeItem = withStyles(fileTreeItemStyle)(
- class extends React.Component<FileTreeItemProps & WithStyles<CssRules>> {
- render() {
- const { classes, item } = this.props;
- return <>
- <div className={classes.root}>
- <ListItemTextIcon
- icon={getIcon(item.data.type)}
- name={item.data.name} />
- <div className={classes.spacer} />
- <Typography
- className={classes.sizeInfo}
- variant="caption">{formatFileSize(item.data.size)}</Typography>
- <Tooltip title="More options" disableFocusListener>
- <IconButton
- data-cy='file-item-options-btn'
- className={classes.button}
- onClick={this.handleClick}>
- <MoreOptionsIcon className={classes.moreOptions} />
- </IconButton>
- </Tooltip>
- </div >
- </>;
- }
-
- handleClick = (event: React.MouseEvent<any>) => {
- this.props.onMoreClick(event, this.props.item);
- }
- });
+import { DirectoryIcon, DefaultIcon, FileIcon } from "../icon/icon";
export const getIcon = (type: string) => {
switch (type) {
+++ /dev/null
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import React from "react";
-import { TreeItem, TreeItemStatus } from "../tree/tree";
-import { VirtualTree as Tree } from "../tree/virtual-tree";
-import { FileTreeData } from "./file-tree-data";
-import { FileTreeItem } from "./file-tree-item";
-
-export interface FileTreeProps {
- items: Array<TreeItem<FileTreeData>>;
- onMenuOpen: (event: React.MouseEvent<HTMLElement>, item: TreeItem<FileTreeData>) => void;
- onSelectionToggle: (event: React.MouseEvent<HTMLElement>, item: TreeItem<FileTreeData>) => void;
- onCollapseToggle: (id: string, status: TreeItemStatus) => void;
- onFileClick: (id: string) => void;
- currentItemUuid?: string;
-}
-
-export class FileTree extends React.Component<FileTreeProps> {
- render() {
- return <Tree
- showSelection={true}
- items={this.props.items}
- disableRipple={true}
- render={this.renderItem}
- onContextMenu={this.handleContextMenu}
- toggleItemActive={this.handleToggleActive}
- toggleItemOpen={this.handleToggle}
- toggleItemSelection={this.handleSelectionChange}
- currentItemUuid={this.props.currentItemUuid} />;
- }
-
- handleContextMenu = (event: React.MouseEvent<any>, item: TreeItem<FileTreeData>) => {
- event.stopPropagation();
- this.props.onMenuOpen(event, item);
- }
-
- handleToggle = (event: React.MouseEvent<{}>, { id, status }: TreeItem<{}>) => {
- this.props.onCollapseToggle(id, status);
- }
-
- handleToggleActive = (_: React.MouseEvent<HTMLElement>, item: TreeItem<FileTreeData>) => {
- this.props.onFileClick(item.id);
- }
-
- handleSelectionChange = (event: React.MouseEvent<HTMLElement>, item: TreeItem<FileTreeData>) => {
- event.stopPropagation();
- this.props.onSelectionToggle(event, item);
- }
-
- renderItem = (item: TreeItem<FileTreeData>) =>
- <FileTreeItem
- item={item}
- onMoreClick={this.handleContextMenu} />
-
-}
open: boolean;
active: boolean;
selected?: boolean;
+ initialState?: boolean;
+ indeterminate?: boolean;
flatTree?: boolean;
status: TreeItemStatus;
items?: Array<TreeItem<T>>;
{showSelection(it) && !useRadioButtons &&
<Checkbox
checked={it.selected}
+ indeterminate={!it.selected && it.indeterminate}
className={classes.checkbox}
color="primary"
onClick={this.handleCheckboxChange(it)} />}
parent: string;
active: boolean;
selected: boolean;
+ initialState?: boolean;
expanded: boolean;
status: TreeNodeStatus;
}
...data,
});
+export const getTreeDirty = (id: string) => <T>(tree: Tree<T>): boolean => {
+ const node = getNode(id)(tree);
+ const children = getNodeDescendants(id)(tree);
+ return (node
+ && node.initialState !== undefined
+ && node.selected !== node.initialState
+ )
+ || children.some(child =>
+ child.initialState !== undefined
+ && child.selected !== child.initialState
+ );
+}
+
const toggleDescendantsSelection = (id: string) => <T>(tree: Tree<T>) => {
const node = getNode(id)(tree);
if (node) {
return tree;
};
-
const mapNodeValue = <T, R>(mapFn: (value: T) => R) => (node: TreeNode<T>): TreeNode<R> =>
({ ...node, value: mapFn(node.value) });
import { getInitialResourceTypeFilters, serializeResourceTypeFilters, ObjectTypeFilter, CollectionTypeFilter, ProcessTypeFilter, GroupTypeFilter, buildProcessStatusFilters, ProcessStatusFilter } from './resource-type-filters';
import { ResourceKind } from 'models/resource';
-import { deselectNode } from 'models/tree';
+import { selectNode, deselectNode } from 'models/tree';
import { pipe } from 'lodash/fp';
import { FilterBuilder } from 'services/api/filter-builder';
const filters = getInitialResourceTypeFilters();
const serializedFilters = serializeResourceTypeFilters(filters);
expect(serializedFilters)
- .toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.PROCESS}","${ResourceKind.COLLECTION}","${ResourceKind.WORKFLOW}"]]`);
+ .toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.COLLECTION}","${ResourceKind.WORKFLOW}","${ResourceKind.PROCESS}"]],["container_requests.requesting_container_uuid","=",null]`);
});
it("should serialize all but collection filters", () => {
const filters = deselectNode(ObjectTypeFilter.COLLECTION)(getInitialResourceTypeFilters());
const serializedFilters = serializeResourceTypeFilters(filters);
expect(serializedFilters)
- .toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.PROCESS}","${ResourceKind.WORKFLOW}"]]`);
+ .toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.WORKFLOW}","${ResourceKind.PROCESS}"]],["container_requests.requesting_container_uuid","=",null]`);
});
it("should serialize output collections and projects", () => {
const filters = pipe(
() => getInitialResourceTypeFilters(),
- deselectNode(ObjectTypeFilter.PROCESS),
- deselectNode(ObjectTypeFilter.WORKFLOW),
+ deselectNode(ObjectTypeFilter.DEFINITION),
+ deselectNode(ProcessTypeFilter.MAIN_PROCESS),
deselectNode(CollectionTypeFilter.GENERAL_COLLECTION),
deselectNode(CollectionTypeFilter.LOG_COLLECTION),
deselectNode(CollectionTypeFilter.INTERMEDIATE_COLLECTION),
it("should serialize intermediate collections and projects", () => {
const filters = pipe(
() => getInitialResourceTypeFilters(),
- deselectNode(ObjectTypeFilter.PROCESS),
- deselectNode(ObjectTypeFilter.WORKFLOW),
+ deselectNode(ObjectTypeFilter.DEFINITION),
+ deselectNode(ProcessTypeFilter.MAIN_PROCESS),
deselectNode(CollectionTypeFilter.GENERAL_COLLECTION),
deselectNode(CollectionTypeFilter.LOG_COLLECTION),
deselectNode(CollectionTypeFilter.OUTPUT_COLLECTION),
const filters = pipe(
() => getInitialResourceTypeFilters(),
deselectNode(ObjectTypeFilter.PROJECT),
- deselectNode(ObjectTypeFilter.PROCESS),
- deselectNode(ObjectTypeFilter.WORKFLOW),
+ deselectNode(ObjectTypeFilter.DEFINITION),
+ deselectNode(ProcessTypeFilter.MAIN_PROCESS),
deselectNode(CollectionTypeFilter.OUTPUT_COLLECTION)
)();
deselectNode(ObjectTypeFilter.PROJECT),
deselectNode(ProcessTypeFilter.CHILD_PROCESS),
deselectNode(ObjectTypeFilter.COLLECTION),
- deselectNode(ObjectTypeFilter.WORKFLOW),
+ deselectNode(ObjectTypeFilter.DEFINITION),
)();
const serializedFilters = serializeResourceTypeFilters(filters);
() => getInitialResourceTypeFilters(),
deselectNode(ObjectTypeFilter.PROJECT),
deselectNode(ProcessTypeFilter.MAIN_PROCESS),
+ deselectNode(ObjectTypeFilter.DEFINITION),
deselectNode(ObjectTypeFilter.COLLECTION),
- deselectNode(ObjectTypeFilter.WORKFLOW),
+
+ selectNode(ProcessTypeFilter.CHILD_PROCESS),
)();
const serializedFilters = serializeResourceTypeFilters(filters);
it("should serialize all project types", () => {
const filters = pipe(
() => getInitialResourceTypeFilters(),
- deselectNode(ObjectTypeFilter.PROCESS),
deselectNode(ObjectTypeFilter.COLLECTION),
- deselectNode(ObjectTypeFilter.WORKFLOW),
+ deselectNode(ObjectTypeFilter.DEFINITION),
+ deselectNode(ProcessTypeFilter.MAIN_PROCESS),
)();
const serializedFilters = serializeResourceTypeFilters(filters);
const filters = pipe(
() => getInitialResourceTypeFilters(),
deselectNode(GroupTypeFilter.PROJECT),
- deselectNode(ObjectTypeFilter.PROCESS),
+ deselectNode(ObjectTypeFilter.DEFINITION),
+ deselectNode(ProcessTypeFilter.MAIN_PROCESS),
deselectNode(ObjectTypeFilter.COLLECTION),
- deselectNode(ObjectTypeFilter.WORKFLOW),
)();
const serializedFilters = serializeResourceTypeFilters(filters);
const filters = pipe(
() => getInitialResourceTypeFilters(),
deselectNode(GroupTypeFilter.FILTER_GROUP),
- deselectNode(ObjectTypeFilter.PROCESS),
+ deselectNode(ObjectTypeFilter.DEFINITION),
+ deselectNode(ProcessTypeFilter.MAIN_PROCESS),
deselectNode(ObjectTypeFilter.COLLECTION),
- deselectNode(ObjectTypeFilter.WORKFLOW),
)();
const serializedFilters = serializeResourceTypeFilters(filters);
export enum ObjectTypeFilter {
PROJECT = 'Project',
- PROCESS = 'Process',
- COLLECTION = 'Data collection',
WORKFLOW = 'Workflow',
+ COLLECTION = 'Data collection',
+ DEFINITION = 'Definition',
}
export enum GroupTypeFilter {
}
export enum ProcessTypeFilter {
- MAIN_PROCESS = 'Main',
- CHILD_PROCESS = 'Child',
+ MAIN_PROCESS = 'Runs',
+ CHILD_PROCESS = 'Intermediate Steps',
}
-const initFilter = (name: string, parent = '', isSelected?: boolean) =>
+const initFilter = (name: string, parent = '', isSelected?: boolean, isExpanded?: boolean) =>
setNode<DataTableFilterItem>({
id: name,
value: { name },
children: [],
active: false,
selected: isSelected !== undefined ? isSelected : true,
- expanded: false,
+ initialState: isSelected !== undefined ? isSelected : true,
+ expanded: isExpanded !== undefined ? isExpanded : false,
status: TreeNodeStatus.LOADED,
});
export const getSimpleObjectTypeFilters = pipe(
(): DataTableFilters => createTree<DataTableFilterItem>(),
initFilter(ObjectTypeFilter.PROJECT),
- initFilter(ObjectTypeFilter.PROCESS),
- initFilter(ObjectTypeFilter.COLLECTION),
initFilter(ObjectTypeFilter.WORKFLOW),
+ initFilter(ObjectTypeFilter.COLLECTION),
+ initFilter(ObjectTypeFilter.DEFINITION),
);
// Using pipe() with more than 7 arguments makes the return type be 'any',
export const getInitialResourceTypeFilters = pipe(
(): DataTableFilters => createTree<DataTableFilterItem>(),
pipe(
- initFilter(ObjectTypeFilter.PROJECT),
+ initFilter(ObjectTypeFilter.PROJECT, '', true, true),
initFilter(GroupTypeFilter.PROJECT, ObjectTypeFilter.PROJECT),
initFilter(GroupTypeFilter.FILTER_GROUP, ObjectTypeFilter.PROJECT),
),
pipe(
- initFilter(ObjectTypeFilter.PROCESS),
- initFilter(ProcessTypeFilter.MAIN_PROCESS, ObjectTypeFilter.PROCESS),
- initFilter(ProcessTypeFilter.CHILD_PROCESS, ObjectTypeFilter.PROCESS)
+ initFilter(ObjectTypeFilter.WORKFLOW, '', false, true),
+ initFilter(ObjectTypeFilter.DEFINITION, ObjectTypeFilter.WORKFLOW),
+ initFilter(ProcessTypeFilter.MAIN_PROCESS, ObjectTypeFilter.WORKFLOW),
+ initFilter(ProcessTypeFilter.CHILD_PROCESS, ObjectTypeFilter.WORKFLOW, false),
),
pipe(
- initFilter(ObjectTypeFilter.COLLECTION),
+ initFilter(ObjectTypeFilter.COLLECTION, '', true, true),
initFilter(CollectionTypeFilter.GENERAL_COLLECTION, ObjectTypeFilter.COLLECTION),
initFilter(CollectionTypeFilter.OUTPUT_COLLECTION, ObjectTypeFilter.COLLECTION),
initFilter(CollectionTypeFilter.INTERMEDIATE_COLLECTION, ObjectTypeFilter.COLLECTION),
initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION),
),
- initFilter(ObjectTypeFilter.WORKFLOW)
);
switch (type) {
case ObjectTypeFilter.PROJECT:
return ResourceKind.PROJECT;
- case ObjectTypeFilter.PROCESS:
+ case ObjectTypeFilter.WORKFLOW:
return ResourceKind.PROCESS;
case ObjectTypeFilter.COLLECTION:
return ResourceKind.COLLECTION;
- case ObjectTypeFilter.WORKFLOW:
+ case ObjectTypeFilter.DEFINITION:
return ResourceKind.WORKFLOW;
}
};
? set.add(ObjectTypeFilter.COLLECTION)
: set,
set => processFilters.length > 0
- ? set.add(ObjectTypeFilter.PROCESS)
+ ? set.add(ObjectTypeFilter.WORKFLOW)
: set,
set => Array.from(set)
)();
return {
fb: typeFilters.length > 0
? fb.addIsA('uuid', typeFilters.map(objectTypeToResourceKind))
- : fb,
+ : fb.addIsA('uuid', ResourceKind.NONE),
selectedFilters,
};
};
const loadSharingDialog = async (dispatch: Dispatch, getState: () => RootState, { apiClientAuthorizationService }: ServiceRepository) => {
const dialog = getDialog<SharingDialogData>(getState().dialog, SHARING_DIALOG_NAME);
+ const sharingURLsDisabled = getState().auth.config.clusterConfig.Workbench.DisableSharingURLsUI;
if (dialog) {
dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME));
try {
const resourceUuid = dialog.data.resourceUuid;
await dispatch<any>(initializeManagementForm);
// For collections, we need to load the public sharing tokens
- if (extractUuidObjectType(resourceUuid) === ResourceObjectType.COLLECTION) {
+ if (!sharingURLsDisabled && extractUuidObjectType(resourceUuid) === ResourceObjectType.COLLECTION) {
const sharingTokens = await apiClientAuthorizationService.listCollectionSharingTokens(resourceUuid);
dispatch(resourcesActions.SET_RESOURCES([...sharingTokens.items]));
}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { mount, configure } from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
+import { Provider } from 'react-redux';
+import { combineReducers, createStore } from 'redux';
+
+import SharingDialogComponent, {
+ SharingDialogComponentProps,
+} from './sharing-dialog-component';
+import {
+ extractUuidObjectType,
+ ResourceObjectType
+} from 'models/resource';
+
+configure({ adapter: new Adapter() });
+
+describe("<SharingDialogComponent />", () => {
+ let props: SharingDialogComponentProps;
+ let store;
+
+ beforeEach(() => {
+ const initialAuthState = {
+ config: {
+ keepWebServiceUrl: 'http://example.com/',
+ keepWebInlineServiceUrl: 'http://*.collections.example.com/',
+ }
+ }
+ store = createStore(combineReducers({
+ auth: (state: any = initialAuthState, action: any) => state,
+ }));
+
+ props = {
+ open: true,
+ loading: false,
+ saveEnabled: false,
+ sharedResourceUuid: 'zzzzz-4zz18-zzzzzzzzzzzzzzz',
+ privateAccess: true,
+ sharingURLsNr: 2,
+ sharingURLsDisabled: false,
+ onClose: jest.fn(),
+ onSave: jest.fn(),
+ onCreateSharingToken: jest.fn(),
+ refreshPermissions: jest.fn(),
+ };
+ });
+
+ it("show sharing urls tab on collections when not disabled", () => {
+ expect(props.sharingURLsDisabled).toBe(false);
+ expect(props.sharingURLsNr).toBe(2);
+ expect(extractUuidObjectType(props.sharedResourceUuid) === ResourceObjectType.COLLECTION).toBe(true);
+ let wrapper = mount(<Provider store={store}><SharingDialogComponent {...props} /></Provider>);
+ expect(wrapper.html()).toContain('Sharing URLs (2)');
+
+ // disable Sharing URLs UI
+ props.sharingURLsDisabled = true;
+ wrapper = mount(<Provider store={store}><SharingDialogComponent {...props} /></Provider>);
+ expect(wrapper.html()).not.toContain('Sharing URLs');
+ });
+
+ it("does not show sharing urls on non-collection resources", () => {
+ props.sharedResourceUuid = 'zzzzz-j7d0g-0123456789abcde';
+ expect(extractUuidObjectType(props.sharedResourceUuid) === ResourceObjectType.COLLECTION).toBe(false);
+ expect(props.sharingURLsDisabled).toBe(false);
+ let wrapper = mount(<Provider store={store}><SharingDialogComponent {...props} /></Provider>);
+ expect(wrapper.html()).not.toContain('Sharing URLs');
+ });
+});
\ No newline at end of file
sharedResourceUuid: string;
sharingURLsNr: number;
privateAccess: boolean;
+ sharingURLsDisabled: boolean;
}
export interface SharingDialogActionProps {
onClose: () => void;
PERMISSIONS = 0,
URLS = 1,
}
-export default (props: SharingDialogDataProps & SharingDialogActionProps) => {
+export type SharingDialogComponentProps = SharingDialogDataProps & SharingDialogActionProps;
+
+export default (props: SharingDialogComponentProps) => {
const { open, loading, saveEnabled, sharedResourceUuid,
- sharingURLsNr, privateAccess,
+ sharingURLsNr, privateAccess, sharingURLsDisabled,
onClose, onSave, onCreateSharingToken, refreshPermissions } = props;
- const showTabs = extractUuidObjectType(sharedResourceUuid) === ResourceObjectType.COLLECTION;
+ const showTabs = !sharingURLsDisabled && extractUuidObjectType(sharedResourceUuid) === ResourceObjectType.COLLECTION;
const [tabNr, setTabNr] = React.useState<number>(SharingDialogTab.PERMISSIONS);
const [expDate, setExpDate] = React.useState<Date>();
const [withExpiration, setWithExpiration] = React.useState<boolean>(false);
</Grid>
</>
}
- { tabNr === SharingDialogTab.PERMISSIONS && privateAccess && sharingURLsNr > 0 &&
+ { tabNr === SharingDialogTab.PERMISSIONS && !sharingURLsDisabled &&
+ privateAccess && sharingURLsNr > 0 &&
<Grid item md={12}>
<Typography variant='caption' align='center' color='error'>
Although there aren't specific permissions set, this is publicly accessible via Sharing URL(s).
const mapStateToProps = (state: RootState, { working, ...props }: Props): SharingDialogDataProps => {
const dialog = getDialog<SharingDialogData>(state.dialog, SHARING_DIALOG_NAME);
const sharedResourceUuid = dialog?.data.resourceUuid || '';
+ const sharingURLsDisabled = state.auth.config.clusterConfig.Workbench.DisableSharingURLsUI;
return ({
...props,
saveEnabled: hasChanges(state),
loading: working,
sharedResourceUuid,
- sharingURLsNr: (filterResources(
- (resource: ApiClientAuthorization) =>
+ sharingURLsDisabled,
+ sharingURLsNr: !sharingURLsDisabled
+ ? (filterResources( (resource: ApiClientAuthorization) =>
resource.kind === ResourceKind.API_CLIENT_AUTHORIZATION &&
resource.scopes.includes(`GET /arvados/v1/collections/${sharedResourceUuid}`) &&
resource.scopes.includes(`GET /arvados/v1/collections/${sharedResourceUuid}/`) &&
resource.scopes.includes('GET /arvados/v1/keep_services/accessible')
- )(state.resources) as ApiClientAuthorization[]).length,
+ )(state.resources) as ApiClientAuthorization[]).length
+ : 0,
privateAccess: getSharingPublicAccessFormData(state)?.visibility === VisibilityLevel.PRIVATE,
})
};
import { navigateTo } from 'store/navigation/navigation-action';
import { ContainerRequestState } from "models/container-request";
import { RootState } from 'store/store';
-import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
import { createTree } from 'models/tree';
import { getInitialProcessStatusFilters, getInitialProcessTypeFilters } from 'store/resource-type-filters/resource-type-filters';
import { getProcess } from 'store/processes/process';
onRowDoubleClick={this.handleRowDoubleClick}
onContextMenu={this.handleContextMenu}
contextMenuColumn={true}
- dataTableDefaultView={ <DataTableDefaultView
- icon={ProcessIcon}
- messages={['Processes list empty.']}
- /> } />
+ defaultViewIcon={ProcessIcon}
+ defaultViewMessages={['Processes list empty.']} />
</div>
}
}
import { createTree } from 'models/tree';
import { DataColumns } from 'components/data-table/data-table';
import { SortDirection } from 'components/data-table/data-column';
-import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
import { API_CLIENT_AUTHORIZATION_PANEL_ID } from '../../store/api-client-authorizations/api-client-authorizations-actions';
import { DataExplorer } from 'views-components/data-explorer/data-explorer';
import { ResourcesState } from 'store/resources/resources';
contextMenuColumn={true}
hideColumnSelector
hideSearchInput
- dataTableDefaultView={
- <DataTableDefaultView
- icon={ShareMeIcon}
- messages={[DEFAULT_MESSAGE]} />
- } /></div>
-);
\ No newline at end of file
+ defaultViewIcon={ShareMeIcon}
+ defaultViewMessages={[DEFAULT_MESSAGE]} />
+ </div>
+);
import { CollectionIcon } from 'components/icon/icon';
import { ArvadosTheme } from 'common/custom-theme';
import { BackIcon } from 'components/icon/icon';
-import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from 'store/collections-content-address-panel/collections-content-address-panel-actions';
import { DataExplorer } from "views-components/data-explorer/data-explorer";
import { Dispatch } from 'redux';
onContextMenu={this.props.onContextMenu(this.props.resources)}
contextMenuColumn={true}
title={`Content address: ${this.props.match.params.id}`}
- dataTableDefaultView={
- <DataTableDefaultView
- icon={CollectionIcon}
- messages={['Collections with this content address not found.']} />
- } /></div>
- </div>;
+ defaultViewIcon={CollectionIcon}
+ defaultViewMessages={['Collections with this content address not found.']} />
+ </div>
+ </div>;
}
}
)
import { ContainerRequestState } from "models/container-request";
import { FavoritesState } from 'store/favorites/favorites-reducer';
import { RootState } from 'store/store';
-import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
import { createTree } from 'models/tree';
import { getSimpleObjectTypeFilters } from 'store/resource-type-filters/resource-type-filters';
import { getResource, ResourcesState } from 'store/resources/resources';
onRowDoubleClick={this.handleRowDoubleClick}
onContextMenu={this.handleContextMenu}
contextMenuColumn={true}
- dataTableDefaultView={
- <DataTableDefaultView
- icon={FavoriteIcon}
- messages={['Your favorites list is empty.']}
- />
- } /></div>;
+ defaultViewIcon={FavoriteIcon}
+ defaultViewMessages={['Your favorites list is empty.']} />
+ </div>;
}
}
)
import { openContextMenu } from 'store/context-menu/context-menu-actions';
import { ResourcesState, getResource } from 'store/resources/resources';
import { Grid, Button, Tabs, Tab, Paper, WithStyles, withStyles, StyleRulesCallback } from '@material-ui/core';
-import { AddIcon } from 'components/icon/icon';
+import { AddIcon, UserPanelIcon, KeyIcon } from 'components/icon/icon';
import { getUserUuid } from 'common/getuser';
import { GroupResource, isBuiltinGroup } from 'models/group';
import { ArvadosTheme } from 'common/custom-theme';
-type CssRules = "root";
+type CssRules = "root" | "content";
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
width: '100%',
+ },
+ content: {
+ // reserve space for the tab bar
+ height: `calc(100% - ${theme.spacing.unit * 7}px)`,
}
});
REMOVE = "Remove",
}
+const MEMBERS_DEFAULT_MESSAGE = 'Members list is empty.';
+const PERMISSIONS_DEFAULT_MESSAGE = 'Permissions list is empty.';
+
export const groupDetailsMembersPanelColumns: DataColumns<string> = [
{
name: GroupDetailsPanelMembersColumnNames.FULL_NAME,
<Tab data-cy="group-details-members-tab" label="MEMBERS" />
<Tab data-cy="group-details-permissions-tab" label="PERMISSIONS" />
</Tabs>
- {value === 0 &&
- <DataExplorer
- id={GROUP_DETAILS_MEMBERS_PANEL_ID}
- data-cy="group-members-data-explorer"
- onRowClick={noop}
- onRowDoubleClick={noop}
- onContextMenu={noop}
- contextMenuColumn={false}
- hideColumnSelector
- hideSearchInput
- actions={
- this.props.groupCanManage &&
- <Grid container justify='flex-end'>
- <Button
- data-cy="group-member-add"
- variant="contained"
- color="primary"
- onClick={this.props.onAddUser}>
- <AddIcon /> Add user
- </Button>
- </Grid>
- }
- paperProps={{
- elevation: 0,
- }} />
- }
- {value === 1 &&
- <DataExplorer
- id={GROUP_DETAILS_PERMISSIONS_PANEL_ID}
- data-cy="group-permissions-data-explorer"
- onRowClick={noop}
- onRowDoubleClick={noop}
- onContextMenu={noop}
- contextMenuColumn={false}
- hideColumnSelector
- hideSearchInput
- paperProps={{
- elevation: 0,
- }} />
- }
+ <div className={this.props.classes.content}>
+ {value === 0 &&
+ <DataExplorer
+ id={GROUP_DETAILS_MEMBERS_PANEL_ID}
+ data-cy="group-members-data-explorer"
+ onRowClick={noop}
+ onRowDoubleClick={noop}
+ onContextMenu={noop}
+ contextMenuColumn={false}
+ defaultViewIcon={UserPanelIcon}
+ defaultViewMessages={[MEMBERS_DEFAULT_MESSAGE]}
+ hideColumnSelector
+ hideSearchInput
+ actions={
+ this.props.groupCanManage &&
+ <Grid container justify='flex-end'>
+ <Button
+ data-cy="group-member-add"
+ variant="contained"
+ color="primary"
+ onClick={this.props.onAddUser}>
+ <AddIcon /> Add user
+ </Button>
+ </Grid>
+ }
+ paperProps={{
+ elevation: 0,
+ }} />
+ }
+ {value === 1 &&
+ <DataExplorer
+ id={GROUP_DETAILS_PERMISSIONS_PANEL_ID}
+ data-cy="group-permissions-data-explorer"
+ onRowClick={noop}
+ onRowDoubleClick={noop}
+ onContextMenu={noop}
+ contextMenuColumn={false}
+ defaultViewIcon={KeyIcon}
+ defaultViewMessages={[PERMISSIONS_DEFAULT_MESSAGE]}
+ hideColumnSelector
+ hideSearchInput
+ paperProps={{
+ elevation: 0,
+ }} />
+ }
+ </div>
</Paper>
);
}
import { DataExplorer } from 'views-components/data-explorer/data-explorer';
import { SortDirection } from 'components/data-table/data-column';
import { DataColumns } from 'components/data-table/data-table';
-import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
import { ResourcesState } from 'store/resources/resources';
import { ShareMeIcon } from 'components/icon/icon';
import { createTree } from 'models/tree';
contextMenuColumn={true}
hideColumnSelector
hideSearchInput
- dataTableDefaultView={
- <DataTableDefaultView
- icon={ShareMeIcon}
- messages={['Your link list is empty.']} />
- }/></div>;
-});
\ No newline at end of file
+ defaultViewIcon={ShareMeIcon}
+ defaultViewMessages={['Your link list is empty.']} />
+ </div>;
+});
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 {
onRowDoubleClick={this.handleRowDoubleClick}
onContextMenu={this.handleContextMenu}
contextMenuColumn={true}
- dataTableDefaultView={
- <DataTableDefaultView
- icon={ProjectIcon}
- messages={DEFAULT_VIEW_MESSAGES} />
- } />
+ defaultViewIcon={ProjectIcon}
+ defaultViewMessages={DEFAULT_VIEW_MESSAGES}
+ />
</div>;
}
import { navigateTo } from 'store/navigation/navigation-action';
import { ContainerRequestState } from "models/container-request";
import { RootState } from 'store/store';
-import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
import { createTree } from 'models/tree';
import { getSimpleObjectTypeFilters } from 'store/resource-type-filters/resource-type-filters';
import { PUBLIC_FAVORITE_PANEL_ID } from 'store/public-favorites-panel/public-favorites-action';
onRowDoubleClick={this.props.onItemDoubleClick}
onContextMenu={this.props.onContextMenu(this.props.resources)}
contextMenuColumn={true}
- dataTableDefaultView={
- <DataTableDefaultView
- icon={PublicFavoriteIcon}
- messages={['Public favorites list is empty.']} />
- } /></div>;
+ defaultViewIcon={PublicFavoriteIcon}
+ defaultViewMessages={['Public favorites list is empty.']} />
+ </div>;
}
}
)
import { ResourcesState, getResource } from 'store/resources/resources';
import { navigateTo } from "store/navigation/navigation-action";
import { loadDetailsPanel } from "store/details-panel/details-panel-action";
-import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
import { SHARED_WITH_ME_PANEL_ID } from 'store/shared-with-me-panel/shared-with-me-panel-actions';
import {
openContextMenu,
onRowDoubleClick={this.handleRowDoubleClick}
onContextMenu={this.handleContextMenu}
contextMenuColumn={false}
- dataTableDefaultView={
- <DataTableDefaultView
- icon={ShareMeIcon}
- messages={['No shared items']} />
- } /></div>;
+ defaultViewIcon={ShareMeIcon}
+ defaultViewMessages={['No shared items']} />
+ </div>;
}
handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
import { ProcessIcon } from 'components/icon/icon';
import { ResourceName } from 'views-components/data-explorer/renderers';
import { SUBPROCESS_PANEL_ID } from 'store/subprocess-panel/subprocess-panel-actions';
-import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
import { createTree } from 'models/tree';
import { getInitialProcessStatusFilters } from 'store/resource-type-filters/resource-type-filters';
import { ResourcesState } from 'store/resources/resources';
onRowDoubleClick={props.onItemDoubleClick}
onContextMenu={(event, item) => props.onContextMenu(event, item, props.resources)}
contextMenuColumn={true}
- dataTableDefaultView={
- <DataTableDefaultView
- icon={ProcessIcon}
- messages={DEFAULT_VIEW_MESSAGES} />
- }
+ defaultViewIcon={ProcessIcon}
+ defaultViewMessages={DEFAULT_VIEW_MESSAGES}
doHidePanel={props.doHidePanel}
doMaximizePanel={props.doMaximizePanel}
panelMaximized={props.panelMaximized}
panelName={props.panelName} />;
-};
\ No newline at end of file
+};
import { toggleTrashed } from "store/trash/trash-actions";
import { ContextMenuKind } from "views-components/context-menu/context-menu";
import { Dispatch } from "redux";
-import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
import { createTree } from 'models/tree';
import {
getTrashPanelTypeFilters
onRowDoubleClick={this.handleRowDoubleClick}
onContextMenu={this.handleContextMenu}
contextMenuColumn={false}
- dataTableDefaultView={
- <DataTableDefaultView
- icon={TrashIcon}
- messages={['Your trash list is empty.']}/>
- } /></div>;
+ defaultViewIcon={TrashIcon}
+ defaultViewMessages={['Your trash list is empty.']} />
+ </div>;
}
handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
UserResourceAccountStatus,
} from "views-components/data-explorer/renderers";
import { navigateToUserProfile } from "store/navigation/navigation-action";
-import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
import { createTree } from 'models/tree';
import { compose, Dispatch } from 'redux';
import { UserResource } from 'models/user';
paperProps={{
elevation: 0,
}}
- dataTableDefaultView={
- <DataTableDefaultView
- icon={ShareMeIcon}
- messages={['Your user list is empty.']} />
- } />
+ defaultViewIcon={ShareMeIcon}
+ defaultViewMessages={['Your user list is empty.']} />
</Paper>;
}
IconButton,
} from '@material-ui/core';
import { ArvadosTheme } from 'common/custom-theme';
-import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
import { PROFILE_EMAIL_VALIDATION, PROFILE_URL_VALIDATION } from "validators/validators";
import { USER_PROFILE_PANEL_ID } from 'store/user-profile/user-profile-actions';
import { noop } from 'lodash';
paperProps={{
elevation: 0,
}}
- dataTableDefaultView={
- <DataTableDefaultView
- icon={GroupsIcon}
- messages={['Group list is empty.']} />
- } />
+ defaultViewIcon={GroupsIcon}
+ defaultViewMessages={['Group list is empty.']} />
</div>}
</Paper >;
}
import React from 'react';
import { DataExplorer } from "views-components/data-explorer/data-explorer";
import { WorkflowIcon } from 'components/icon/icon';
-import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
import { WORKFLOW_PANEL_ID } from 'store/workflow-panel/workflow-panel-actions';
import {
ResourceLastModifiedDate,
onRowDoubleClick={props.handleRowDoubleClick}
contextMenuColumn={false}
onContextMenu={e => e}
- dataTableDefaultView={<DataTableDefaultView icon={WorkflowIcon} />} />
+ defaultViewIcon={WorkflowIcon}
+ defaultViewMessages={['Workflow list is empty.']} />
</Grid>
<Grid item xs={6}>
<Paper style={{ height: '100%' }}>