Merge branch '14258-update-project-panel-to-use-filters-tree' into 14258-collection...
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Mon, 26 Nov 2018 13:56:43 +0000 (14:56 +0100)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Mon, 26 Nov 2018 13:56:43 +0000 (14:56 +0100)
refs #14258
14258

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

13 files changed:
src/components/data-table-filters/data-table-filters-tree.tsx
src/components/tree/tree.tsx
src/models/tree.ts
src/services/api/filter-builder.test.ts
src/services/api/filter-builder.ts
src/store/favorite-panel/favorite-panel-middleware-service.ts
src/store/project-panel/project-panel-action.ts
src/store/project-panel/project-panel-middleware-service.ts
src/store/resource-type-filters/resource-type-filters.test.ts
src/store/resource-type-filters/resource-type-filters.ts
src/store/trash-panel/trash-panel-middleware-service.ts
src/views/favorite-panel/favorite-panel.tsx
src/views/project-panel/project-panel.tsx

index 0870497ee2ece84caeebaff670aca1855c5f4e1d..b13224badc43d3ba87db688da3e893d50d7ec9fb 100644 (file)
@@ -7,6 +7,7 @@ import { Tree, toggleNodeSelection, getNode, initTreeNode, getNodeChildrenIds }
 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;
@@ -22,8 +23,12 @@ export interface DataTableFilterProps {
 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}
index 4cbefbd2b0eb0cbb1a0b9cc8d5927cc66a05e90c..c64a722139b3d4c5a9690e1ee0120e673c52cf7f 100644 (file)
@@ -88,6 +88,8 @@ export interface TreeProps<T> {
     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;
@@ -103,10 +105,16 @@ export const Tree = withStyles(styles)(
                 ? 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)}>
index 8b71692b1c8bf8211c606573f99a8876a6392aab..bec2f758a478335e6ef437afe6592dde144ce729 100644 (file)
@@ -98,6 +98,9 @@ export const getNodeDescendants = (id: string, limit = Infinity) => <T>(tree: Tr
 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 :
index e365b331dd11e31493ac02eb689baaa91b0363af..13fde867024b42422f30606ee82e073652836617 100644 (file)
@@ -60,6 +60,12 @@ describe("FilterBuilder", () => {
         ).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
index f83f574faf007fa948442cf32710c15c17cd9750..1ebf488636115c7dfb2cd9bcbf420d79ee82fa1b 100644 (file)
@@ -31,6 +31,10 @@ export class FilterBuilder {
         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);
     }
index 0d75ad1f23597985a3941d399909af22150c696b..87f49f34f281221da6bb3ea7e33df5b3852d520d 100644 (file)
@@ -2,8 +2,8 @@
 //
 // 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";
@@ -21,6 +21,8 @@ import { progressIndicatorActions } from '~/store/progress-indicator/progress-in
 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) {
@@ -34,7 +36,8 @@ export class FavoritePanelMiddlewareService extends DataExplorerMiddlewareServic
         } 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>();
@@ -59,10 +62,10 @@ export class FavoritePanelMiddlewareService extends DataExplorerMiddlewareServic
                         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));
index ef720923b317c0c0c25a4724b63988c0a6c52c1b..21598fad1c6072eb7de02ac7d64215af1f42bfa9 100644 (file)
@@ -2,10 +2,9 @@
 //
 // 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";
 
@@ -15,7 +14,7 @@ export const IS_PROJECT_PANEL_TRASHED = 'isProjectPanelTrashed';
 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());
     };
index 3d0a6c4ba5e40bdbe27634daafa2a9ea8a720e31..36672e99ac19003ede9f9ade9240d870de496d14 100644 (file)
@@ -14,7 +14,7 @@ import { DataColumns } from "~/components/data-table/data-table";
 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';
@@ -32,6 +32,7 @@ import { getResource } from "~/store/resources/resources";
 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) {
@@ -116,15 +117,21 @@ export const getParams = (dataExplorer: DataExplorer, isProjectTrashed: boolean)
 
 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) => {
index 3e3458605a8ebf24e47bdfc4a8c8ca1d4071d699..02f017edf309026b33786809a7f07c74b8725453 100644 (file)
@@ -12,7 +12,7 @@ describe("serializeResourceTypeFilters", () => {
         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", () => {
@@ -34,4 +34,17 @@ describe("serializeResourceTypeFilters", () => {
         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"]]`);
+    });
 });
index d95ae5a4e04109efba1b5d919aaec04ea107b1fa..78777be10941a0e34d8b46a186fed8a8f6bfbd4f 100644 (file)
@@ -2,8 +2,8 @@
 //
 // 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';
@@ -35,6 +35,13 @@ const initFilter = (name: string, parent = '') =>
         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),
@@ -98,16 +105,37 @@ const serializeCollectionTypeFilters = ({ fb, selectedFilters }: ReturnType<type
     () => 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);
+};
index e6cee25ef559b54233bfb8889dfe7708982fe0e3..f52421a1d6581581cdf97f9613d848d6b3f74e15 100644 (file)
@@ -23,6 +23,9 @@ import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions
 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) {
@@ -33,7 +36,20 @@ export class TrashPanelMiddlewareService extends DataExplorerMiddlewareService {
         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>();
 
@@ -55,13 +71,7 @@ export class TrashPanelMiddlewareService extends DataExplorerMiddlewareService {
                 .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
                 });
index 40bf9a0c4399ce79b7daeb5435ebad9800295bb0..4682d3fc299bf6cdc6b3e8e24768a8fdc5f5f161 100644 (file)
@@ -11,7 +11,6 @@ import { RouteComponentProps } from 'react-router';
 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 {
@@ -28,12 +27,12 @@ import { openContextMenu, resourceKindToContextMenuKind } from '~/store/context-
 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) => ({
@@ -79,7 +78,7 @@ export const favoritePanelColumns: DataColumns<string> = [
         name: FavoritePanelColumnNames.TYPE,
         selected: true,
         configurable: true,
-        filters: getInitialResourceTypeFilters(),
+        filters: getSimpleObjectTypeFilters(),
         render: uuid => <ResourceType uuid={uuid} />
     },
     {
index ffc1a7ab3d150b8fa5d2bca821788e6f4191b547..b8811476668fea67ef2abc23422759af88176086 100644 (file)
@@ -3,16 +3,18 @@
 // 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';
@@ -24,12 +26,10 @@ 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 { 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) => ({
@@ -105,6 +105,11 @@ export const projectPanelColumns: DataColumns<string> = [
 
 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;
@@ -131,11 +136,8 @@ export const ProjectPanel = withStyles(styles)(
                         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>;
             }