import { Tree as TreeComponent, TreeItem, TreeItemStatus } from '~/components/tree/tree';
import { noop, map } from "lodash/fp";
import { toggleNodeCollapse } from '~/models/tree';
+import { countNodes, countChildren } from '~/models/tree';
export interface DataTableFilterItem {
name: string;
export class DataTableFiltersTree extends React.Component<DataTableFilterProps> {
render() {
+ const { filters } = this.props;
+ const hasSubfilters = countNodes(filters) !== countChildren('')(filters);
return <TreeComponent
- items={filtersToTree(this.props.filters)}
+ levelIndentation={hasSubfilters ? 20 : 0}
+ itemRightPadding={20}
+ items={filtersToTree(filters)}
render={renderItem}
showSelection
onContextMenu={noop}
onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
render: (item: TreeItem<T>, level?: number) => ReactElement<{}>;
showSelection?: boolean | ((item: TreeItem<T>) => boolean);
+ levelIndentation?: number;
+ itemRightPadding?: number;
toggleItemActive: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
toggleItemOpen: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
toggleItemSelection?: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
? this.props.showSelection
: () => this.props.showSelection ? true : false;
+ const { levelIndentation = 20, itemRightPadding = 20 } = this.props;
+
return <List component="div" className={list}>
{items && items.map((it: TreeItem<T>, idx: number) =>
<div key={`item/${level}/${idx}`}>
- <ListItem button className={listItem} style={{ paddingLeft: (level + 1) * 20 }}
+ <ListItem button className={listItem}
+ style={{
+ paddingLeft: (level + 1) * levelIndentation,
+ paddingRight: itemRightPadding,
+ }}
disableRipple={disableRipple}
onClick={event => toggleItemActive(event, it)}
onContextMenu={this.handleRowContextMenu(it)}>
export const countNodes = <T>(tree: Tree<T>) =>
getNodeDescendantsIds('')(tree).length;
+export const countChildren = (id: string) => <T>(tree: Tree<T>) =>
+ getNodeChildren('')(tree).length;
+
export const getNodeDescendantsIds = (id: string, limit = Infinity) => <T>(tree: Tree<T>): string[] => {
const node = getNode(id)(tree);
const children = node ? node.children :
).toEqual(`["etag","in",["etagValue1","etagValue2"]]`);
});
+ it("should add 'not in' rule for set", () => {
+ expect(
+ filters.addIn("etag", ["etagValue1", "etagValue2"]).getFilters()
+ ).toEqual(`["etag","not in",["etagValue1","etagValue2"]]`);
+ });
+
it("should add multiple rules", () => {
expect(
filters
return this.addCondition(field, "in", value, "", "", resourcePrefix);
}
+ public addNotIn(field: string, value?: string | string[], resourcePrefix?: string) {
+ return this.addCondition(field, "not in", value, "", "", resourcePrefix);
+ }
+
public addGt(field: string, value?: string, resourcePrefix?: string) {
return this.addCondition(field, ">", value, "", "", resourcePrefix);
}
//
// SPDX-License-Identifier: AGPL-3.0
-import { DataExplorerMiddlewareService } from "../data-explorer/data-explorer-middleware-service";
-import { FavoritePanelColumnNames, FavoritePanelFilter } from "~/views/favorite-panel/favorite-panel";
+import { DataExplorerMiddlewareService } from "~/store/data-explorer/data-explorer-middleware-service";
+import { FavoritePanelColumnNames } from "~/views/favorite-panel/favorite-panel";
import { RootState } from "../store";
import { DataColumns } from "~/components/data-table/data-table";
import { ServiceRepository } from "~/services/services";
import { getDataExplorer } from "~/store/data-explorer/data-explorer-reducer";
import { loadMissingProcessesInformation } from "~/store/project-panel/project-panel-middleware-service";
import { getSortColumn } from "~/store/data-explorer/data-explorer-reducer";
+import { getDataExplorerColumnFilters } from '~/store/data-explorer/data-explorer-middleware-service';
+import { serializeSimpleObjectTypeFilters } from '../resource-type-filters/resource-type-filters';
export class FavoritePanelMiddlewareService extends DataExplorerMiddlewareService {
constructor(private services: ServiceRepository, id: string) {
} else {
const columns = dataExplorer.columns as DataColumns<string>;
const sortColumn = getSortColumn(dataExplorer);
- const typeFilters = this.getColumnFilters(columns, FavoritePanelColumnNames.TYPE);
+ const typeFilters = serializeSimpleObjectTypeFilters(getDataExplorerColumnFilters(columns, FavoritePanelColumnNames.TYPE));
+
const linkOrder = new OrderBuilder<LinkResource>();
const contentOrder = new OrderBuilder<GroupContentsResource>();
linkOrder: linkOrder.getOrder(),
contentOrder: contentOrder.getOrder(),
filters: new FilterBuilder()
- // TODO: update filters
- // .addIsA("headUuid", typeFilters.map(filter => filter.type))
.addILike("name", dataExplorer.searchValue)
- .getFilters()
+ .addIsA("headUuid", typeFilters)
+ .getFilters(),
+
});
api.dispatch(progressIndicatorActions.PERSIST_STOP_WORKING(this.getId()));
api.dispatch(resourcesActions.SET_RESOURCES(response.items));
//
// SPDX-License-Identifier: AGPL-3.0
-import { bindDataExplorerActions } from "../data-explorer/data-explorer-action";
-import { propertiesActions } from "~/store/properties/properties-actions";
import { Dispatch } from 'redux';
-import { ServiceRepository } from "~/services/services";
+import { bindDataExplorerActions } from "~/store/data-explorer/data-explorer-action";
+import { propertiesActions } from "~/store/properties/properties-actions";
import { RootState } from '~/store/store';
import { getProperty } from "~/store/properties/properties";
export const projectPanelActions = bindDataExplorerActions(PROJECT_PANEL_ID);
export const openProjectPanel = (projectUuid: string) =>
- (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ (dispatch: Dispatch) => {
dispatch(propertiesActions.SET_PROPERTY({ key: PROJECT_PANEL_CURRENT_UUID, value: projectUuid }));
dispatch(projectPanelActions.REQUEST_ITEMS());
};
import { ServiceRepository } from "~/services/services";
import { SortDirection } from "~/components/data-table/data-column";
import { OrderBuilder, OrderDirection } from "~/services/api/order-builder";
-import { FilterBuilder } from "~/services/api/filter-builder";
+import { FilterBuilder, joinFilters } from "~/services/api/filter-builder";
import { GroupContentsResource, GroupContentsResourcePrefix } from "~/services/groups-service/groups-service";
import { updateFavorites } from "../favorites/favorites-actions";
import { PROJECT_PANEL_CURRENT_UUID, IS_PROJECT_PANEL_TRASHED, projectPanelActions } from './project-panel-action';
import { CollectionResource } from "~/models/collection";
import { resourcesDataActions } from "~/store/resources-data/resources-data-actions";
import { getSortColumn } from "~/store/data-explorer/data-explorer-reducer";
+import { serializeResourceTypeFilters } from '../resource-type-filters/resource-type-filters';
export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService {
constructor(private services: ServiceRepository, id: string) {
export const getFilters = (dataExplorer: DataExplorer) => {
const columns = dataExplorer.columns as DataColumns<string>;
- const typeFilters = getDataExplorerColumnFilters(columns, ProjectPanelColumnNames.TYPE);
- const statusFilters = getDataExplorerColumnFilters(columns, ProjectPanelColumnNames.STATUS);
- return new FilterBuilder()
- // TODO: update filters
- // .addIsA("uuid", typeFilters.map(f => f.type))
+ const typeFilters = serializeResourceTypeFilters(getDataExplorerColumnFilters(columns, ProjectPanelColumnNames.TYPE));
+
+ // TODO: Extract group contents name filter
+ const nameFilters = new FilterBuilder()
.addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.COLLECTION)
.addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROCESS)
.addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROJECT)
.getFilters();
+
+ return joinFilters(
+ typeFilters,
+ nameFilters,
+ );
+ // TODO: Restore process status filters
+ // const statusFilters = getDataExplorerColumnFilters(columns, ProjectPanelColumnNames.STATUS);
};
export const getOrder = (dataExplorer: DataExplorer) => {
const filters = getInitialResourceTypeFilters();
const serializedFilters = serializeResourceTypeFilters(filters);
expect(serializedFilters)
- .toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.PROCESS}","${ResourceKind.COLLECTION}"]],["collections.properties.type","in",["nil","output","log"]]`);
+ .toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.PROCESS}","${ResourceKind.COLLECTION}"]]`);
});
it("should serialize all but collection filters", () => {
expect(serializedFilters)
.toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.COLLECTION}"]],["collections.properties.type","in",["output"]]`);
});
+
+ it("should serialize general and log collections", () => {
+ const filters = pipe(
+ () => getInitialResourceTypeFilters(),
+ deselectNode(ObjectTypeFilter.PROJECT),
+ deselectNode(ObjectTypeFilter.PROCESS),
+ deselectNode(CollectionTypeFilter.OUTPUT_COLLECTION)
+ )();
+
+ const serializedFilters = serializeResourceTypeFilters(filters);
+ expect(serializedFilters)
+ .toEqual(`["uuid","is_a",["${ResourceKind.COLLECTION}"]],["collections.properties.type","not in",["output"]]`);
+ });
});
//
// SPDX-License-Identifier: AGPL-3.0
-import { pipe, values, includes, __ } from 'lodash/fp';
-import { createTree, setNode, TreeNodeStatus, TreeNode } from '~/models/tree';
+import { difference, pipe, values, includes, __ } from 'lodash/fp';
+import { createTree, setNode, TreeNodeStatus, TreeNode, Tree } from '~/models/tree';
import { DataTableFilterItem, DataTableFilters } from '~/components/data-table-filters/data-table-filters-tree';
import { ResourceKind } from '~/models/resource';
import { FilterBuilder } from '~/services/api/filter-builder';
status: TreeNodeStatus.LOADED,
});
+export const getSimpleObjectTypeFilters = pipe(
+ (): DataTableFilters => createTree<DataTableFilterItem>(),
+ initFilter(ObjectTypeFilter.PROJECT),
+ initFilter(ObjectTypeFilter.PROCESS),
+ initFilter(ObjectTypeFilter.COLLECTION),
+);
+
export const getInitialResourceTypeFilters = pipe(
(): DataTableFilters => createTree<DataTableFilterItem>(),
initFilter(ObjectTypeFilter.PROJECT),
() => getMatchingFilters(values(CollectionTypeFilter), selectedFilters),
filters => filters.map(collectionTypeToPropertyValue),
mappedFilters => ({
- fb: mappedFilters.length > 0
- ? fb.addIn('type', mappedFilters, `${GroupContentsResourcePrefix.COLLECTION}.properties`)
- : fb,
+ fb: buildCollectiomTypeFilters({ fb, filters: mappedFilters }),
selectedFilters
})
)();
+const COLLECTION_TYPES = values(CollectionType);
+
+const NON_GENERAL_COLLECTION_TYPES = difference(COLLECTION_TYPES, [CollectionType.GENERAL]);
+
+const COLLECTION_PROPERTIES_PREFIX = `${GroupContentsResourcePrefix.COLLECTION}.properties`;
+
+const buildCollectiomTypeFilters = ({ fb, filters }: { fb: FilterBuilder, filters: CollectionType[] }) => {
+ switch (true) {
+ case filters.length === 0 || filters.length === COLLECTION_TYPES.length:
+ return fb;
+ case includes(CollectionType.GENERAL, filters):
+ return fb.addNotIn('type', difference(NON_GENERAL_COLLECTION_TYPES, filters), COLLECTION_PROPERTIES_PREFIX);
+ default:
+ return fb.addIn('type', filters, COLLECTION_PROPERTIES_PREFIX);
+ }
+};
+
export const serializeResourceTypeFilters = pipe(
createFiltersBuilder,
serializeObjectTypeFilters,
serializeCollectionTypeFilters,
({ fb }) => fb.getFilters(),
);
+
+export const serializeSimpleObjectTypeFilters = (filters: Tree<DataTableFilterItem>) => {
+ return getSelectedNodes(filters)
+ .map(f => f.id)
+ .map(objectTypeToResourceKind);
+};
import { updateResources } from "~/store/resources/resources-actions";
import { progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions";
import { getSortColumn } from "~/store/data-explorer/data-explorer-reducer";
+import { serializeResourceTypeFilters } from '~/store//resource-type-filters/resource-type-filters';
+import { getDataExplorerColumnFilters } from '~/store/data-explorer/data-explorer-middleware-service';
+import { joinFilters } from '../../services/api/filter-builder';
export class TrashPanelMiddlewareService extends DataExplorerMiddlewareService {
constructor(private services: ServiceRepository, id: string) {
const dataExplorer = api.getState().dataExplorer[this.getId()];
const columns = dataExplorer.columns as DataColumns<string>;
const sortColumn = getSortColumn(dataExplorer);
- const typeFilters = this.getColumnFilters(columns, TrashPanelColumnNames.TYPE);
+
+ const typeFilters = serializeResourceTypeFilters(getDataExplorerColumnFilters(columns, ProjectPanelColumnNames.TYPE));
+
+ const otherFilters = new FilterBuilder()
+ .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.COLLECTION)
+ .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROCESS)
+ .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROJECT)
+ .addEqual("is_trashed", true)
+ .getFilters();
+
+ const filters = joinFilters(
+ typeFilters,
+ otherFilters,
+ );
const order = new OrderBuilder<ProjectResource>();
.contents(userUuid, {
...dataExplorerToListParams(dataExplorer),
order: order.getOrder(),
- filters: new FilterBuilder()
- // TODO: update filters
- // .addIsA("uuid", typeFilters.map(f => f.type))
- .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.COLLECTION)
- .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROJECT)
- .addEqual("is_trashed", true)
- .getFilters(),
+ filters,
recursive: true,
includeTrash: true
});
import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
import { SortDirection } from '~/components/data-table/data-column';
import { ResourceKind } from '~/models/resource';
-import { resourceLabel } from '~/common/labels';
import { ArvadosTheme } from '~/common/custom-theme';
import { FAVORITE_PANEL_ID } from "~/store/favorite-panel/favorite-panel-action";
import {
import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
import { navigateTo } from '~/store/navigation/navigation-action';
import { ContainerRequestState } from "~/models/container-request";
-import { FavoritesState } from '../../store/favorites/favorites-reducer';
+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 { getInitialResourceTypeFilters } from '../../store/resource-type-filters/resource-type-filters';
-// TODO: clean up code
+import { getSimpleObjectTypeFilters } from '~/store/resource-type-filters/resource-type-filters';
+
type CssRules = "toolbar" | "button";
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
name: FavoritePanelColumnNames.TYPE,
selected: true,
configurable: true,
- filters: getInitialResourceTypeFilters(),
+ filters: getSimpleObjectTypeFilters(),
render: uuid => <ResourceType uuid={uuid} />
},
{
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
+import withStyles from "@material-ui/core/styles/withStyles";
import { DispatchProp, connect } from 'react-redux';
-import { DataColumns } from '~/components/data-table/data-table';
import { RouteComponentProps } from 'react-router';
+import { StyleRulesCallback, WithStyles } 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 { resourceLabel } from '~/common/labels';
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 { 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 { StyleRulesCallback, WithStyles } from "@material-ui/core";
import { ArvadosTheme } from "~/common/custom-theme";
-import withStyles from "@material-ui/core/styles/withStyles";
import { createTree } from '~/models/tree';
-import { getInitialResourceTypeFilters } from '../../store/resource-type-filters/resource-type-filters';
-// TODO: code cleanup
+import { getInitialResourceTypeFilters } from '~/store/resource-type-filters/resource-type-filters';
+
type CssRules = 'root' | "button";
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
export const PROJECT_PANEL_ID = "projectPanel";
+const DEFAUL_VIEW_MESSAGES = [
+ 'Your project is empty.',
+ 'Please create a project or create a collection and upload a data.',
+];
+
interface ProjectPanelDataProps {
currentItemId: string;
resources: ResourcesState;
dataTableDefaultView={
<DataTableDefaultView
icon={ProjectIcon}
- messages={[
- 'Your project is empty.',
- 'Please create a project or create a collection and upload a data.'
- ]}/>
- }/>
+ messages={DEFAUL_VIEW_MESSAGES} />
+ } />
</div>;
}