[ARVADOS-WORKBENCH2] updated: 1.2.0-376-g6f8e509

Git user git at public.curoverse.com
Sun Sep 16 15:57:04 EDT 2018


Summary of changes:
 package.json                                       |  4 ++-
 src/index.tsx                                      |  6 ++--
 .../empty.ts => services/api/api-progress.ts}      |  5 +--
 src/services/api/url-builder.test.ts               | 36 +++++++++++++++++++
 src/services/api/url-builder.ts                    | 21 ++++++++++++
 src/services/auth-service/auth-service.ts          | 28 ++++++++++-----
 .../collection-service/collection-service.ts       |  5 +--
 .../common-service/common-resource-service.test.ts | 17 +++++----
 .../common-service/common-resource-service.ts      | 40 +++++++++++++++++-----
 .../common-service/trashable-resource-service.ts   | 25 ++++++++------
 .../container-request-service.ts                   |  7 ++--
 .../container-service/container-service.ts         |  7 ++--
 src/services/favorite-service/favorite-service.ts  |  2 +-
 src/services/groups-service/groups-service.test.ts |  2 +-
 src/services/groups-service/groups-service.ts      | 17 +++++----
 src/services/keep-service/keep-service.ts          |  5 +--
 src/services/link-service/link-service.ts          |  5 +--
 src/services/log-service/log-service.ts            |  5 +--
 .../project-service/project-service.test.ts        |  4 +--
 src/services/services.ts                           | 30 ++++++++--------
 src/services/user-service/user-service.ts          |  5 +--
 src/store/auth/auth-actions.test.ts                |  5 +--
 src/store/auth/auth-reducer.test.ts                |  3 +-
 .../progress-indicator-actions.ts                  |  7 ++--
 .../progress-indicator-reducer.ts                  | 38 +++++---------------
 src/store/progress-indicator/with-progress.ts      | 38 ++++++++++----------
 .../side-panel-tree/side-panel-tree-actions.ts     |  2 --
 src/views-components/main-app-bar/main-app-bar.tsx |  5 ++-
 src/views-components/progress/content-progress.tsx | 24 ++++++-------
 .../progress/side-panel-progress.tsx               | 24 ++++++-------
 src/views-components/side-panel/side-panel.tsx     |  6 ++--
 src/views/workbench/workbench.tsx                  | 25 +++++++-------
 tslint.json                                        |  3 +-
 yarn.lock                                          | 14 +++++---
 34 files changed, 283 insertions(+), 187 deletions(-)
 copy src/{models/empty.ts => services/api/api-progress.ts} (57%)
 create mode 100644 src/services/api/url-builder.test.ts

       via  6f8e509988756deb7e05a760e718d1ade164fd19 (commit)
      from  8e6af2106e745285c5cda437d5856a99111686f8 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit 6f8e509988756deb7e05a760e718d1ade164fd19
Author: Daniel Kos <daniel.kos at contractors.roche.com>
Date:   Sun Sep 16 21:55:57 2018 +0200

    Add progress indicator for services
    
    Feature #14186
    
    Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos at contractors.roche.com>

diff --git a/package.json b/package.json
index 84d1510..dd16eea 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,8 @@
     "react-transition-group": "2.4.0",
     "redux": "4.0.0",
     "redux-thunk": "2.3.0",
-    "unionize": "2.1.2"
+    "unionize": "2.1.2",
+    "uuid": "3.3.2"
   },
   "scripts": {
     "start": "react-scripts-ts start",
@@ -47,6 +48,7 @@
     "@types/react-router-redux": "5.0.15",
     "@types/redux-devtools": "3.0.44",
     "@types/redux-form": "7.4.5",
+    "@types/uuid": "3.4.4",
     "axios-mock-adapter": "1.15.0",
     "enzyme": "3.4.4",
     "enzyme-adapter-react-16": "1.2.0",
diff --git a/src/index.tsx b/src/index.tsx
index a070685..2fb236d 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -38,7 +38,6 @@ import { addRouteChangeHandlers } from './routes/route-change-handlers';
 import { setCurrentTokenDialogApiHost } from '~/store/current-token-dialog/current-token-dialog-actions';
 import { processResourceActionSet } from './views-components/context-menu/action-sets/process-resource-action-set';
 import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
-import { ProgressIndicatorData } from '~/store/progress-indicator/progress-indicator-reducer';
 
 const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev");
 const getGitCommit = () => "GIT-" + (process.env.REACT_APP_GIT_COMMIT || "latest").substr(0, 7);
@@ -63,13 +62,14 @@ addMenuActionSet(ContextMenuKind.TRASH, trashActionSet);
 fetchConfig()
     .then(({ config, apiHost }) => {
         const history = createBrowserHistory();
-        const services = createServices(config);
+        const services = createServices(config, (id, working) => {
+            store.dispatch(progressIndicatorActions.TOGGLE({ id, working }));
+        });
         const store = configureStore(history, services);
 
         store.subscribe(initListener(history, store, services, config));
         store.dispatch(initAuth());
         store.dispatch(setCurrentTokenDialogApiHost(apiHost));
-        store.dispatch(progressIndicatorActions.START_SUBMIT({ id: ProgressIndicatorData.SIDE_PANEL_PROGRESS }));
 
         const TokenComponent = (props: any) => <ApiToken authService={services.authService} {...props} />;
         const WorkbenchComponent = (props: any) => <Workbench authService={services.authService} buildInfo={buildInfo} {...props} />;
diff --git a/src/services/api/api-progress.ts b/src/services/api/api-progress.ts
new file mode 100644
index 0000000..14dc584
--- /dev/null
+++ b/src/services/api/api-progress.ts
@@ -0,0 +1,5 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export type ProgressFn = (id: string, working: boolean) => void;
diff --git a/src/services/api/url-builder.test.ts b/src/services/api/url-builder.test.ts
new file mode 100644
index 0000000..2b48940
--- /dev/null
+++ b/src/services/api/url-builder.test.ts
@@ -0,0 +1,36 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { OrderBuilder } from "./order-builder";
+import { joinUrls } from "~/services/api/url-builder";
+
+describe("UrlBuilder", () => {
+    it("should join urls properly 1", () => {
+        expect(joinUrls('http://localhost:3000', '/main')).toEqual('http://localhost:3000/main');
+    });
+    it("should join urls properly 2", () => {
+        expect(joinUrls('http://localhost:3000/', '/main')).toEqual('http://localhost:3000/main');
+    });
+    it("should join urls properly 3", () => {
+        expect(joinUrls('http://localhost:3000//', '/main')).toEqual('http://localhost:3000/main');
+    });
+    it("should join urls properly 4", () => {
+        expect(joinUrls('http://localhost:3000', '//main')).toEqual('http://localhost:3000/main');
+    });
+    it("should join urls properly 5", () => {
+        expect(joinUrls('http://localhost:3000///', 'main')).toEqual('http://localhost:3000/main');
+    });
+    it("should join urls properly 6", () => {
+        expect(joinUrls('http://localhost:3000///', '//main')).toEqual('http://localhost:3000/main');
+    });
+    it("should join urls properly 7", () => {
+        expect(joinUrls(undefined, '//main')).toEqual('/main');
+    });
+    it("should join urls properly 8", () => {
+        expect(joinUrls(undefined, 'main')).toEqual('/main');
+    });
+    it("should join urls properly 9", () => {
+        expect(joinUrls('http://localhost:3000///', undefined)).toEqual('http://localhost:3000');
+    });
+});
diff --git a/src/services/api/url-builder.ts b/src/services/api/url-builder.ts
index 0587c83..32039a5 100644
--- a/src/services/api/url-builder.ts
+++ b/src/services/api/url-builder.ts
@@ -24,3 +24,24 @@ export class UrlBuilder {
         return this.url + this.query;
     }
 }
+
+export function joinUrls(url0?: string, url1?: string) {
+    let u0 = "";
+    if (url0) {
+        let idx0 = url0.length - 1;
+        while (url0[idx0] === '/') { --idx0; }
+        u0 = url0.substr(0, idx0 + 1);
+    }
+    let u1 = "";
+    if (url1) {
+        let idx1 = 0;
+        while (url1[idx1] === '/') { ++idx1; }
+        u1 = url1.substr(idx1);
+    }
+    let url = u0;
+    if (u1.length > 0) {
+        url += '/';
+    }
+    url += u1;
+    return url;
+}
diff --git a/src/services/auth-service/auth-service.ts b/src/services/auth-service/auth-service.ts
index 57915f7..89545c1 100644
--- a/src/services/auth-service/auth-service.ts
+++ b/src/services/auth-service/auth-service.ts
@@ -4,6 +4,8 @@
 
 import { User } from "~/models/user";
 import { AxiosInstance } from "axios";
+import { ProgressFn } from "~/services/api/api-progress";
+import * as uuid from "uuid/v4";
 
 export const API_TOKEN_KEY = 'apiToken';
 export const USER_EMAIL_KEY = 'userEmail';
@@ -25,7 +27,8 @@ export class AuthService {
 
     constructor(
         protected apiClient: AxiosInstance,
-        protected baseUrl: string) { }
+        protected baseUrl: string,
+        protected progressFn: ProgressFn) { }
 
     public saveApiToken(token: string) {
         localStorage.setItem(API_TOKEN_KEY, token);
@@ -86,15 +89,24 @@ export class AuthService {
     }
 
     public getUserDetails = (): Promise<User> => {
+        const reqId = uuid();
+        this.progressFn(reqId, true);
         return this.apiClient
             .get<UserDetailsResponse>('/users/current')
-            .then(resp => ({
-                email: resp.data.email,
-                firstName: resp.data.first_name,
-                lastName: resp.data.last_name,
-                uuid: resp.data.uuid,
-                ownerUuid: resp.data.owner_uuid
-            }));
+            .then(resp => {
+                this.progressFn(reqId, false);
+                return {
+                    email: resp.data.email,
+                    firstName: resp.data.first_name,
+                    lastName: resp.data.last_name,
+                    uuid: resp.data.uuid,
+                    ownerUuid: resp.data.owner_uuid
+                };
+            })
+            .catch(e => {
+                this.progressFn(reqId, false);
+                throw e;
+            });
     }
 
     public getRootUuid() {
diff --git a/src/services/collection-service/collection-service.ts b/src/services/collection-service/collection-service.ts
index 6e6f2a9..6a60ebf 100644
--- a/src/services/collection-service/collection-service.ts
+++ b/src/services/collection-service/collection-service.ts
@@ -11,12 +11,13 @@ import { mapTreeValues } from "~/models/tree";
 import { parseFilesResponse } from "./collection-service-files-response";
 import { fileToArrayBuffer } from "~/common/file";
 import { TrashableResourceService } from "~/services/common-service/trashable-resource-service";
+import { ProgressFn } from "~/services/api/api-progress";
 
 export type UploadProgress = (fileId: number, loaded: number, total: number, currentTime: number) => void;
 
 export class CollectionService extends TrashableResourceService<CollectionResource> {
-    constructor(serverApi: AxiosInstance, private webdavClient: WebDAV, private authService: AuthService) {
-        super(serverApi, "collections");
+    constructor(serverApi: AxiosInstance, private webdavClient: WebDAV, private authService: AuthService, progressFn: ProgressFn) {
+        super(serverApi, "collections", progressFn);
     }
 
     async files(uuid: string) {
diff --git a/src/services/common-service/common-resource-service.test.ts b/src/services/common-service/common-resource-service.test.ts
index d67d5db..385485d 100644
--- a/src/services/common-service/common-resource-service.test.ts
+++ b/src/services/common-service/common-resource-service.test.ts
@@ -6,11 +6,13 @@ import { CommonResourceService } from "./common-resource-service";
 import axios, { AxiosInstance } from "axios";
 import MockAdapter from "axios-mock-adapter";
 import { Resource } from "src/models/resource";
+import { ProgressFn } from "~/services/api/api-progress";
 
-export const mockResourceService = <R extends Resource, C extends CommonResourceService<R>>(Service: new (client: AxiosInstance) => C) => {
+export const mockResourceService = <R extends Resource, C extends CommonResourceService<R>>(
+    Service: new (client: AxiosInstance, progressFn: ProgressFn) => C) => {
     const axiosInstance = axios.create();
     const axiosMock = new MockAdapter(axiosInstance);
-    const service = new Service(axiosInstance);
+    const service = new Service(axiosInstance, (id, working) => {});
     Object.keys(service).map(key => service[key] = jest.fn());
     return service;
 };
@@ -18,6 +20,7 @@ export const mockResourceService = <R extends Resource, C extends CommonResource
 describe("CommonResourceService", () => {
     const axiosInstance = axios.create();
     const axiosMock = new MockAdapter(axiosInstance);
+    const progressFn = (id: string, working: boolean) => {};
 
     beforeEach(() => {
         axiosMock.reset();
@@ -28,14 +31,14 @@ describe("CommonResourceService", () => {
             .onPost("/resource/")
             .reply(200, { owner_uuid: "ownerUuidValue" });
 
-        const commonResourceService = new CommonResourceService(axiosInstance, "resource");
+        const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
         const resource = await commonResourceService.create({ ownerUuid: "ownerUuidValue" });
         expect(resource).toEqual({ ownerUuid: "ownerUuidValue" });
     });
 
     it("#create maps request params to snake case", async () => {
         axiosInstance.post = jest.fn(() => Promise.resolve({data: {}}));
-        const commonResourceService = new CommonResourceService(axiosInstance, "resource");
+        const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
         await commonResourceService.create({ ownerUuid: "ownerUuidValue" });
         expect(axiosInstance.post).toHaveBeenCalledWith("/resource/", {owner_uuid: "ownerUuidValue"});
     });
@@ -45,7 +48,7 @@ describe("CommonResourceService", () => {
             .onDelete("/resource/uuid")
             .reply(200, { deleted_at: "now" });
 
-        const commonResourceService = new CommonResourceService(axiosInstance, "resource");
+        const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
         const resource = await commonResourceService.delete("uuid");
         expect(resource).toEqual({ deletedAt: "now" });
     });
@@ -55,7 +58,7 @@ describe("CommonResourceService", () => {
             .onGet("/resource/uuid")
             .reply(200, { modified_at: "now" });
 
-        const commonResourceService = new CommonResourceService(axiosInstance, "resource");
+        const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
         const resource = await commonResourceService.get("uuid");
         expect(resource).toEqual({ modifiedAt: "now" });
     });
@@ -73,7 +76,7 @@ describe("CommonResourceService", () => {
                 items_available: 20
             });
 
-        const commonResourceService = new CommonResourceService(axiosInstance, "resource");
+        const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
         const resource = await commonResourceService.list({ limit: 10, offset: 1 });
         expect(resource).toEqual({
             kind: "kind",
diff --git a/src/services/common-service/common-resource-service.ts b/src/services/common-service/common-resource-service.ts
index 09e034f..0ad6fbc 100644
--- a/src/services/common-service/common-resource-service.ts
+++ b/src/services/common-service/common-resource-service.ts
@@ -5,6 +5,8 @@
 import * as _ from "lodash";
 import { AxiosInstance, AxiosPromise } from "axios";
 import { Resource } from "src/models/resource";
+import * as uuid from "uuid/v4";
+import { ProgressFn } from "~/services/api/api-progress";
 
 export interface ListArguments {
     limit?: number;
@@ -60,36 +62,53 @@ export class CommonResourceService<T extends Resource> {
             }
         }
 
-    static defaultResponse<R>(promise: AxiosPromise<R>): Promise<R> {
+    static defaultResponse<R>(promise: AxiosPromise<R>, progressFn: ProgressFn): Promise<R> {
+        const reqId = uuid();
+        progressFn(reqId, true);
         return promise
+            .then(data => {
+                progressFn(reqId, false);
+                return data;
+            })
             .then(CommonResourceService.mapResponseKeys)
-            .catch(({ response }) => Promise.reject<Errors>(CommonResourceService.mapResponseKeys(response)));
+            .catch(({ response }) => {
+                progressFn(reqId, false);
+                Promise.reject<Errors>(CommonResourceService.mapResponseKeys(response));
+            });
     }
 
     protected serverApi: AxiosInstance;
     protected resourceType: string;
+    protected progressFn: ProgressFn;
 
-    constructor(serverApi: AxiosInstance, resourceType: string) {
+    constructor(serverApi: AxiosInstance, resourceType: string, onProgress: ProgressFn) {
         this.serverApi = serverApi;
         this.resourceType = '/' + resourceType + '/';
+        this.progressFn = onProgress;
     }
 
     create(data?: Partial<T> | any) {
         return CommonResourceService.defaultResponse(
             this.serverApi
-                .post<T>(this.resourceType, data && CommonResourceService.mapKeys(_.snakeCase)(data)));
+                .post<T>(this.resourceType, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
+            this.progressFn
+        );
     }
 
     delete(uuid: string): Promise<T> {
         return CommonResourceService.defaultResponse(
             this.serverApi
-                .delete(this.resourceType + uuid));
+                .delete(this.resourceType + uuid),
+            this.progressFn
+        );
     }
 
     get(uuid: string) {
         return CommonResourceService.defaultResponse(
             this.serverApi
-                .get<T>(this.resourceType + uuid));
+                .get<T>(this.resourceType + uuid),
+            this.progressFn
+        );
     }
 
     list(args: ListArguments = {}): Promise<ListResults<T>> {
@@ -103,14 +122,17 @@ export class CommonResourceService<T extends Resource> {
             this.serverApi
                 .get(this.resourceType, {
                     params: CommonResourceService.mapKeys(_.snakeCase)(params)
-                }));
+                }),
+            this.progressFn
+        );
     }
 
     update(uuid: string, data: Partial<T>) {
         return CommonResourceService.defaultResponse(
             this.serverApi
-                .put<T>(this.resourceType + uuid, data && CommonResourceService.mapKeys(_.snakeCase)(data)));
-
+                .put<T>(this.resourceType + uuid, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
+            this.progressFn
+        );
     }
 }
 
diff --git a/src/services/common-service/trashable-resource-service.ts b/src/services/common-service/trashable-resource-service.ts
index 23e7366..92e0273 100644
--- a/src/services/common-service/trashable-resource-service.ts
+++ b/src/services/common-service/trashable-resource-service.ts
@@ -6,27 +6,32 @@ import * as _ from "lodash";
 import { AxiosInstance } from "axios";
 import { TrashableResource } from "src/models/resource";
 import { CommonResourceService } from "~/services/common-service/common-resource-service";
+import { ProgressFn } from "~/services/api/api-progress";
 
 export class TrashableResourceService<T extends TrashableResource> extends CommonResourceService<T> {
 
-    constructor(serverApi: AxiosInstance, resourceType: string) {
-        super(serverApi, resourceType);
+    constructor(serverApi: AxiosInstance, resourceType: string, progressFn: ProgressFn) {
+        super(serverApi, resourceType, progressFn);
     }
 
     trash(uuid: string): Promise<T> {
-        return this.serverApi
-            .post(this.resourceType + `${uuid}/trash`)
-            .then(CommonResourceService.mapResponseKeys);
+        return CommonResourceService.defaultResponse(
+            this.serverApi
+                .post(this.resourceType + `${uuid}/trash`),
+            this.progressFn
+        );
     }
 
     untrash(uuid: string): Promise<T> {
         const params = {
             ensure_unique_name: true
         };
-        return this.serverApi
-            .post(this.resourceType + `${uuid}/untrash`, {
-                params: CommonResourceService.mapKeys(_.snakeCase)(params)
-            })
-            .then(CommonResourceService.mapResponseKeys);
+        return CommonResourceService.defaultResponse(
+            this.serverApi
+                .post(this.resourceType + `${uuid}/untrash`, {
+                    params: CommonResourceService.mapKeys(_.snakeCase)(params)
+                }),
+            this.progressFn
+        );
     }
 }
diff --git a/src/services/container-request-service/container-request-service.ts b/src/services/container-request-service/container-request-service.ts
index 01805ff..6ee44d2 100644
--- a/src/services/container-request-service/container-request-service.ts
+++ b/src/services/container-request-service/container-request-service.ts
@@ -4,10 +4,11 @@
 
 import { CommonResourceService } from "~/services/common-service/common-resource-service";
 import { AxiosInstance } from "axios";
-import { ContainerRequestResource } from '../../models/container-request';
+import { ContainerRequestResource } from '~/models/container-request';
+import { ProgressFn } from "~/services/api/api-progress";
 
 export class ContainerRequestService extends CommonResourceService<ContainerRequestResource> {
-    constructor(serverApi: AxiosInstance) {
-        super(serverApi, "container_requests");
+    constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+        super(serverApi, "container_requests", progressFn);
     }
 }
diff --git a/src/services/container-service/container-service.ts b/src/services/container-service/container-service.ts
index 0ace1f6..2f5b712 100644
--- a/src/services/container-service/container-service.ts
+++ b/src/services/container-service/container-service.ts
@@ -4,10 +4,11 @@
 
 import { CommonResourceService } from "~/services/common-service/common-resource-service";
 import { AxiosInstance } from "axios";
-import { ContainerResource } from '../../models/container';
+import { ContainerResource } from '~/models/container';
+import { ProgressFn } from "~/services/api/api-progress";
 
 export class ContainerService extends CommonResourceService<ContainerResource> {
-    constructor(serverApi: AxiosInstance) {
-        super(serverApi, "containers");
+    constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+        super(serverApi, "containers", progressFn);
     }
 }
diff --git a/src/services/favorite-service/favorite-service.ts b/src/services/favorite-service/favorite-service.ts
index 4601054..92b0713 100644
--- a/src/services/favorite-service/favorite-service.ts
+++ b/src/services/favorite-service/favorite-service.ts
@@ -19,7 +19,7 @@ export interface FavoriteListArguments {
 export class FavoriteService {
     constructor(
         private linkService: LinkService,
-        private groupsService: GroupsService
+        private groupsService: GroupsService,
     ) {}
 
     create(data: { userUuid: string; resource: { uuid: string; name: string } }) {
diff --git a/src/services/groups-service/groups-service.test.ts b/src/services/groups-service/groups-service.test.ts
index e1157f4..d88b3c5 100644
--- a/src/services/groups-service/groups-service.test.ts
+++ b/src/services/groups-service/groups-service.test.ts
@@ -27,7 +27,7 @@ describe("GroupsService", () => {
                 items_available: 20
             });
 
-        const groupsService = new GroupsService(axios);
+        const groupsService = new GroupsService(axios, (id, working) => {});
         const resource = await groupsService.contents("1", { limit: 10, offset: 1 });
         expect(resource).toEqual({
             kind: "kind",
diff --git a/src/services/groups-service/groups-service.ts b/src/services/groups-service/groups-service.ts
index b285e92..fe337ef 100644
--- a/src/services/groups-service/groups-service.ts
+++ b/src/services/groups-service/groups-service.ts
@@ -10,6 +10,7 @@ import { ProjectResource } from "~/models/project";
 import { ProcessResource } from "~/models/process";
 import { TrashableResource } from "~/models/resource";
 import { TrashableResourceService } from "~/services/common-service/trashable-resource-service";
+import { ProgressFn } from "~/services/api/api-progress";
 
 export interface ContentsArguments {
     limit?: number;
@@ -27,8 +28,8 @@ export type GroupContentsResource =
 
 export class GroupsService<T extends TrashableResource = TrashableResource> extends TrashableResourceService<T> {
 
-    constructor(serverApi: AxiosInstance) {
-        super(serverApi, "groups");
+    constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+        super(serverApi, "groups", progressFn);
     }
 
     contents(uuid: string, args: ContentsArguments = {}): Promise<ListResults<GroupContentsResource>> {
@@ -38,11 +39,13 @@ export class GroupsService<T extends TrashableResource = TrashableResource> exte
             filters: filters ? `[${filters}]` : undefined,
             order: order ? order : undefined
         };
-        return this.serverApi
-            .get(this.resourceType + `${uuid}/contents`, {
-                params: CommonResourceService.mapKeys(_.snakeCase)(params)
-            })
-            .then(CommonResourceService.mapResponseKeys);
+        return CommonResourceService.defaultResponse(
+            this.serverApi
+                .get(this.resourceType + `${uuid}/contents`, {
+                    params: CommonResourceService.mapKeys(_.snakeCase)(params)
+                }),
+            this.progressFn
+        );
     }
 }
 
diff --git a/src/services/keep-service/keep-service.ts b/src/services/keep-service/keep-service.ts
index 77d06d9..f28629f 100644
--- a/src/services/keep-service/keep-service.ts
+++ b/src/services/keep-service/keep-service.ts
@@ -5,9 +5,10 @@
 import { CommonResourceService } from "~/services/common-service/common-resource-service";
 import { AxiosInstance } from "axios";
 import { KeepResource } from "~/models/keep";
+import { ProgressFn } from "~/services/api/api-progress";
 
 export class KeepService extends CommonResourceService<KeepResource> {
-    constructor(serverApi: AxiosInstance) {
-        super(serverApi, "keep_services");
+    constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+        super(serverApi, "keep_services", progressFn);
     }
 }
diff --git a/src/services/link-service/link-service.ts b/src/services/link-service/link-service.ts
index c77def5..67c1a87 100644
--- a/src/services/link-service/link-service.ts
+++ b/src/services/link-service/link-service.ts
@@ -5,9 +5,10 @@
 import { CommonResourceService } from "~/services/common-service/common-resource-service";
 import { LinkResource } from "~/models/link";
 import { AxiosInstance } from "axios";
+import { ProgressFn } from "~/services/api/api-progress";
 
 export class LinkService extends CommonResourceService<LinkResource> {
-    constructor(serverApi: AxiosInstance) {
-        super(serverApi, "links");
+    constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+        super(serverApi, "links", progressFn);
     }
 }
diff --git a/src/services/log-service/log-service.ts b/src/services/log-service/log-service.ts
index 8f6c66c..7f78d95 100644
--- a/src/services/log-service/log-service.ts
+++ b/src/services/log-service/log-service.ts
@@ -5,9 +5,10 @@
 import { AxiosInstance } from "axios";
 import { LogResource } from '~/models/log';
 import { CommonResourceService } from "~/services/common-service/common-resource-service";
+import { ProgressFn } from "~/services/api/api-progress";
 
 export class LogService extends CommonResourceService<LogResource> {
-    constructor(serverApi: AxiosInstance) {
-        super(serverApi, "logs");
+    constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+        super(serverApi, "logs", progressFn);
     }
 }
diff --git a/src/services/project-service/project-service.test.ts b/src/services/project-service/project-service.test.ts
index 11c2f61..5647ded 100644
--- a/src/services/project-service/project-service.test.ts
+++ b/src/services/project-service/project-service.test.ts
@@ -11,7 +11,7 @@ describe("CommonResourceService", () => {
 
     it(`#create has groupClass set to "project"`, async () => {
         axiosInstance.post = jest.fn(() => Promise.resolve({ data: {} }));
-        const projectService = new ProjectService(axiosInstance);
+        const projectService = new ProjectService(axiosInstance, (id, working) => {});
         const resource = await projectService.create({ name: "nameValue" });
         expect(axiosInstance.post).toHaveBeenCalledWith("/groups/", {
             name: "nameValue",
@@ -21,7 +21,7 @@ describe("CommonResourceService", () => {
 
     it("#list has groupClass filter set by default", async () => {
         axiosInstance.get = jest.fn(() => Promise.resolve({ data: {} }));
-        const projectService = new ProjectService(axiosInstance);
+        const projectService = new ProjectService(axiosInstance, (id, working) => {});
         const resource = await projectService.list();
         expect(axiosInstance.get).toHaveBeenCalledWith("/groups/", {
             params: {
diff --git a/src/services/services.ts b/src/services/services.ts
index 53721dd..bd73c74 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -12,8 +12,8 @@ import { CollectionService } from "./collection-service/collection-service";
 import { TagService } from "./tag-service/tag-service";
 import { CollectionFilesService } from "./collection-files-service/collection-files-service";
 import { KeepService } from "./keep-service/keep-service";
-import { WebDAV } from "../common/webdav";
-import { Config } from "../common/config";
+import { WebDAV } from "~/common/webdav";
+import { Config } from "~/common/config";
 import { UserService } from './user-service/user-service';
 import { AncestorService } from "~/services/ancestors-service/ancestors-service";
 import { ResourceKind } from "~/models/resource";
@@ -23,25 +23,25 @@ import { LogService } from './log-service/log-service';
 
 export type ServiceRepository = ReturnType<typeof createServices>;
 
-export const createServices = (config: Config) => {
+export const createServices = (config: Config, progressFn: (id: string, working: boolean) => void) => {
     const apiClient = Axios.create();
     apiClient.defaults.baseURL = config.baseUrl;
 
     const webdavClient = new WebDAV();
     webdavClient.defaults.baseURL = config.keepWebServiceUrl;
 
-    const containerRequestService = new ContainerRequestService(apiClient);
-    const containerService = new ContainerService(apiClient);
-    const groupsService = new GroupsService(apiClient);
-    const keepService = new KeepService(apiClient);
-    const linkService = new LinkService(apiClient);
-    const logService = new LogService(apiClient);
-    const projectService = new ProjectService(apiClient);
-    const userService = new UserService(apiClient);
-    
+    const containerRequestService = new ContainerRequestService(apiClient, progressFn);
+    const containerService = new ContainerService(apiClient, progressFn);
+    const groupsService = new GroupsService(apiClient, progressFn);
+    const keepService = new KeepService(apiClient, progressFn);
+    const linkService = new LinkService(apiClient, progressFn);
+    const logService = new LogService(apiClient, progressFn);
+    const projectService = new ProjectService(apiClient, progressFn);
+    const userService = new UserService(apiClient, progressFn);
+
     const ancestorsService = new AncestorService(groupsService, userService);
-    const authService = new AuthService(apiClient, config.rootUrl);
-    const collectionService = new CollectionService(apiClient, webdavClient, authService);
+    const authService = new AuthService(apiClient, config.rootUrl, progressFn);
+    const collectionService = new CollectionService(apiClient, webdavClient, authService, progressFn);
     const collectionFilesService = new CollectionFilesService(collectionService);
     const favoriteService = new FavoriteService(linkService, groupsService);
     const tagService = new TagService(linkService);
@@ -77,4 +77,4 @@ export const getResourceService = (kind?: ResourceKind) => (serviceRepository: S
         default:
             return undefined;
     }
-};
\ No newline at end of file
+};
diff --git a/src/services/user-service/user-service.ts b/src/services/user-service/user-service.ts
index 31cc4bb..cd8b6a4 100644
--- a/src/services/user-service/user-service.ts
+++ b/src/services/user-service/user-service.ts
@@ -5,9 +5,10 @@
 import { AxiosInstance } from "axios";
 import { CommonResourceService } from "~/services/common-service/common-resource-service";
 import { UserResource } from "~/models/user";
+import { ProgressFn } from "~/services/api/api-progress";
 
 export class UserService extends CommonResourceService<UserResource> {
-    constructor(serverApi: AxiosInstance) {
-        super(serverApi, "users");
+    constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
+        super(serverApi, "users", progressFn);
     }
 }
diff --git a/src/store/auth/auth-actions.test.ts b/src/store/auth/auth-actions.test.ts
index 4ac48a0..46d2835 100644
--- a/src/store/auth/auth-actions.test.ts
+++ b/src/store/auth/auth-actions.test.ts
@@ -22,11 +22,12 @@ import { mockConfig } from '~/common/config';
 describe('auth-actions', () => {
     let reducer: (state: AuthState | undefined, action: AuthAction) => any;
     let store: RootStore;
+    const progressFn = (id: string, working: boolean) => {};
 
     beforeEach(() => {
-        store = configureStore(createBrowserHistory(), createServices(mockConfig({})));
+        store = configureStore(createBrowserHistory(), createServices(mockConfig({}), progressFn));
         localStorage.clear();
-        reducer = authReducer(createServices(mockConfig({})));
+        reducer = authReducer(createServices(mockConfig({}), progressFn));
     });
 
     it('should initialise state with user and api token from local storage', () => {
diff --git a/src/store/auth/auth-reducer.test.ts b/src/store/auth/auth-reducer.test.ts
index 2b1920a..c8e2ccb 100644
--- a/src/store/auth/auth-reducer.test.ts
+++ b/src/store/auth/auth-reducer.test.ts
@@ -11,10 +11,11 @@ import { mockConfig } from '~/common/config';
 
 describe('auth-reducer', () => {
     let reducer: (state: AuthState | undefined, action: AuthAction) => any;
+    const progressFn = (id: string, working: boolean) => {};
 
     beforeAll(() => {
         localStorage.clear();
-        reducer = authReducer(createServices(mockConfig({})));
+        reducer = authReducer(createServices(mockConfig({}), progressFn));
     });
 
     it('should correctly initialise state', () => {
diff --git a/src/store/progress-indicator/progress-indicator-actions.ts b/src/store/progress-indicator/progress-indicator-actions.ts
index 5f824e4..3712e41 100644
--- a/src/store/progress-indicator/progress-indicator-actions.ts
+++ b/src/store/progress-indicator/progress-indicator-actions.ts
@@ -5,8 +5,9 @@
 import { unionize, ofType, UnionOf } from "~/common/unionize";
 
 export const progressIndicatorActions = unionize({
-    START_SUBMIT: ofType<{ id: string }>(),
-    STOP_SUBMIT: ofType<{ id: string }>()
+    START: ofType<string>(),
+    STOP: ofType<string>(),
+    TOGGLE: ofType<{ id: string, working: boolean }>()
 });
 
-export type ProgressIndicatorAction = UnionOf<typeof progressIndicatorActions>;
\ No newline at end of file
+export type ProgressIndicatorAction = UnionOf<typeof progressIndicatorActions>;
diff --git a/src/store/progress-indicator/progress-indicator-reducer.ts b/src/store/progress-indicator/progress-indicator-reducer.ts
index daacdb7..190ad13 100644
--- a/src/store/progress-indicator/progress-indicator-reducer.ts
+++ b/src/store/progress-indicator/progress-indicator-reducer.ts
@@ -3,45 +3,25 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { ProgressIndicatorAction, progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions";
-import { Dispatch } from 'redux';
-import { RootState } from '~/store/store';
-import { ServiceRepository } from '~/services/services';
 
 export interface ProgressIndicatorState {
-    'sidePanelProgress': { started: boolean };
-    'contentProgress': { started: boolean };
-    // 'workbenchProgress': { started: boolean };
+    [key: string]: {
+        working: boolean
+    };
 }
 
 const initialState: ProgressIndicatorState = {
-    'sidePanelProgress': { started: false },
-    'contentProgress': { started: false },
-    // 'workbenchProgress': { started: false }
 };
 
-export enum ProgressIndicatorData {
-    SIDE_PANEL_PROGRESS = 'sidePanelProgress',
-    CONTENT_PROGRESS = 'contentProgress',
-    // WORKBENCH_PROGRESS = 'workbenchProgress',
-}
-
 export const progressIndicatorReducer = (state: ProgressIndicatorState = initialState, action: ProgressIndicatorAction) => {
     return progressIndicatorActions.match(action, {
-        START_SUBMIT: ({ id }) => ({ ...state, [id]: { started: true } }),
-        STOP_SUBMIT: ({ id }) => ({
-            ...state,
-            [id]: state[id] ? { ...state[id], started: false } : { started: false }
-        }),
+        START: id => ({ ...state, [id]: { working: true } }),
+        STOP: id => ({ ...state, [id]: { working: false } }),
+        TOGGLE: ({ id, working }) => ({ ...state, [id]: { working }}),
         default: () => state,
     });
 };
 
-// export const getProgress = () =>
-//     (dispatch: Dispatch, getState: () => RootState) => {
-//         const progress = getState().progressIndicator;
-//         if (progress.sidePanelProgress.started || progress.contentProgress.started) {
-//             dispatch(progressIndicatorActions.START_SUBMIT({ id: ProgressIndicatorData.WORKBENCH_PROGRESS }));
-//         } else {
-//             dispatch(progressIndicatorActions.STOP_SUBMIT({ id: ProgressIndicatorData.WORKBENCH_PROGRESS }));
-//         }
-//     };
+export function isSystemWorking(state: ProgressIndicatorState): boolean {
+    return Object.keys(state).reduce((working, k) => working ? true : state[k].working, false);
+}
diff --git a/src/store/progress-indicator/with-progress.ts b/src/store/progress-indicator/with-progress.ts
index b91c05d..976f757 100644
--- a/src/store/progress-indicator/with-progress.ts
+++ b/src/store/progress-indicator/with-progress.ts
@@ -1,20 +1,20 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
+// // Copyright (C) The Arvados Authors. All rights reserved.
+// //
+// // SPDX-License-Identifier: AGPL-3.0
 //
-// SPDX-License-Identifier: AGPL-3.0
-
-import * as React from 'react';
-import { connect } from 'react-redux';
-import { RootState } from '~/store/store';
-
-export type WithProgressStateProps = {
-    started: boolean;
-};
-
-export const withProgress = (id: string) =>
-    (component: React.ComponentType<WithProgressStateProps>) =>
-        connect(mapStateToProps(id))(component);
-
-export const mapStateToProps = (id: string) => (state: RootState): WithProgressStateProps => {
-    const progress = state.progressIndicator[id];
-    return progress;
-};
\ No newline at end of file
+// import * as React from 'react';
+// import { connect } from 'react-redux';
+// import { RootState } from '~/store/store';
+//
+// export type WithProgressStateProps = {
+//     started: boolean;
+// };
+//
+// export const withProgress = (id: string) =>
+//     (component: React.ComponentType<WithProgressStateProps>) =>
+//         connect(mapStateToProps(id))(component);
+//
+// export const mapStateToProps = (id: string) => (state: RootState): WithProgressStateProps => {
+//     const progress = state.progressIndicator[id];
+//     return progress;
+// };
diff --git a/src/store/side-panel-tree/side-panel-tree-actions.ts b/src/store/side-panel-tree/side-panel-tree-actions.ts
index c7ad91b..561df1d 100644
--- a/src/store/side-panel-tree/side-panel-tree-actions.ts
+++ b/src/store/side-panel-tree/side-panel-tree-actions.ts
@@ -14,7 +14,6 @@ import { TreeItemStatus } from "~/components/tree/tree";
 import { getNodeAncestors, getNodeValue, getNodeAncestorsIds, getNode } from '~/models/tree';
 import { ProjectResource } from '~/models/project';
 import { progressIndicatorActions } from '../progress-indicator/progress-indicator-actions';
-import { ProgressIndicatorData } from '~/store/progress-indicator/progress-indicator-reducer';
 
 export enum SidePanelTreeCategory {
     PROJECTS = 'Projects',
@@ -101,7 +100,6 @@ export const activateSidePanelTreeItem = (nodeId: string) =>
         if (!isSidePanelTreeCategory(nodeId)) {
             await dispatch<any>(activateSidePanelTreeProject(nodeId));
         }
-        dispatch(progressIndicatorActions.STOP_SUBMIT({ id: ProgressIndicatorData.SIDE_PANEL_PROGRESS }));
     };
 
 export const activateSidePanelTreeProject = (nodeId: string) =>
diff --git a/src/views-components/main-app-bar/main-app-bar.tsx b/src/views-components/main-app-bar/main-app-bar.tsx
index ec2a511..93cf496 100644
--- a/src/views-components/main-app-bar/main-app-bar.tsx
+++ b/src/views-components/main-app-bar/main-app-bar.tsx
@@ -13,6 +13,7 @@ import { NotificationsMenu } from "~/views-components/main-app-bar/notifications
 import { AccountMenu } from "~/views-components/main-app-bar/account-menu";
 import { AnonymousMenu } from "~/views-components/main-app-bar/anonymous-menu";
 import { HelpMenu } from './help-menu';
+import { ReactNode } from "react";
 
 type CssRules = 'toolbar' | 'link';
 
@@ -31,6 +32,7 @@ interface MainAppBarDataProps {
     searchDebounce?: number;
     user?: User;
     buildInfo?: string;
+    children?: ReactNode;
 }
 
 export interface MainAppBarActionProps {
@@ -41,7 +43,7 @@ export type MainAppBarProps = MainAppBarDataProps & MainAppBarActionProps & With
 
 export const MainAppBar = withStyles(styles)(
     (props: MainAppBarProps) => {
-        return <AppBar position="static">
+        return <AppBar position="absolute">
             <Toolbar className={props.classes.toolbar}>
                 <Grid container justify="space-between">
                     <Grid container item xs={3} direction="column" justify="center">
@@ -80,6 +82,7 @@ export const MainAppBar = withStyles(styles)(
                     </Grid>
                 </Grid>
             </Toolbar>
+            {props.children}
         </AppBar>;
     }
 );
diff --git a/src/views-components/progress/content-progress.tsx b/src/views-components/progress/content-progress.tsx
index 0d291f4..fa2cad5 100644
--- a/src/views-components/progress/content-progress.tsx
+++ b/src/views-components/progress/content-progress.tsx
@@ -1,13 +1,13 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
+// // Copyright (C) The Arvados Authors. All rights reserved.
+// //
+// // SPDX-License-Identifier: AGPL-3.0
 //
-// SPDX-License-Identifier: AGPL-3.0
-
-import * as React from 'react';
-import { CircularProgress } from '@material-ui/core';
-import { withProgress } from '~/store/progress-indicator/with-progress';
-import { WithProgressStateProps } from '~/store/progress-indicator/with-progress';
-import { ProgressIndicatorData } from '~/store/progress-indicator/progress-indicator-reducer';
-
-export const ContentProgress = withProgress(ProgressIndicatorData.CONTENT_PROGRESS)((props: WithProgressStateProps) => 
-    props.started ? <CircularProgress /> : null
-);
+// import * as React from 'react';
+// import { CircularProgress } from '@material-ui/core';
+// import { withProgress } from '~/store/progress-indicator/with-progress';
+// import { WithProgressStateProps } from '~/store/progress-indicator/with-progress';
+// import { ProgressIndicatorData } from '~/store/progress-indicator/progress-indicator-reducer';
+//
+// export const ContentProgress = withProgress(ProgressIndicatorData.CONTENT_PROGRESS)((props: WithProgressStateProps) =>
+//     props.started ? <CircularProgress /> : null
+// );
diff --git a/src/views-components/progress/side-panel-progress.tsx b/src/views-components/progress/side-panel-progress.tsx
index b3bc0db..2d832a5 100644
--- a/src/views-components/progress/side-panel-progress.tsx
+++ b/src/views-components/progress/side-panel-progress.tsx
@@ -1,13 +1,13 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
+// // Copyright (C) The Arvados Authors. All rights reserved.
+// //
+// // SPDX-License-Identifier: AGPL-3.0
 //
-// SPDX-License-Identifier: AGPL-3.0
-
-import * as React from 'react';
-import { CircularProgress } from '@material-ui/core';
-import { withProgress } from '~/store/progress-indicator/with-progress';
-import { WithProgressStateProps } from '~/store/progress-indicator/with-progress';
-import { ProgressIndicatorData } from '~/store/progress-indicator/progress-indicator-reducer';
-
-export const SidePanelProgress = withProgress(ProgressIndicatorData.SIDE_PANEL_PROGRESS)((props: WithProgressStateProps) =>
-    props.started ? <span style={{ display: 'flex', justifyContent: 'center', marginTop: "40px" }}><CircularProgress /></span> : null
-);
+// import * as React from 'react';
+// import { CircularProgress } from '@material-ui/core';
+// import { withProgress } from '~/store/progress-indicator/with-progress';
+// import { WithProgressStateProps } from '~/store/progress-indicator/with-progress';
+// import { ProgressIndicatorData } from '~/store/progress-indicator/progress-indicator-reducer';
+//
+// export const SidePanelProgress = withProgress(ProgressIndicatorData.SIDE_PANEL_PROGRESS)((props: WithProgressStateProps) =>
+//     props.started ? <span style={{ display: 'flex', justifyContent: 'center', marginTop: "40px" }}><CircularProgress /></span> : null
+// );
diff --git a/src/views-components/side-panel/side-panel.tsx b/src/views-components/side-panel/side-panel.tsx
index 780ecc7..739e9ea 100644
--- a/src/views-components/side-panel/side-panel.tsx
+++ b/src/views-components/side-panel/side-panel.tsx
@@ -12,7 +12,6 @@ import { navigateFromSidePanel } from '../../store/side-panel/side-panel-action'
 import { Grid } from '@material-ui/core';
 import { SidePanelButton } from '~/views-components/side-panel-button/side-panel-button';
 import { RootState } from '~/store/store';
-import { SidePanelProgress } from '~/views-components/progress/side-panel-progress';
 
 const DRAWER_WITDH = 240;
 
@@ -35,7 +34,6 @@ const mapDispatchToProps = (dispatch: Dispatch): SidePanelTreeProps => ({
 });
 
 const mapStateToProps = (state: RootState) => ({
-    sidePanelProgress: state.progressIndicator.sidePanelProgress.started
 });
 
 export const SidePanel = compose(
@@ -44,5 +42,5 @@ export const SidePanel = compose(
 )(({ classes, ...props }: WithStyles<CssRules> & SidePanelTreeProps) =>
     <Grid item xs>
         <SidePanelButton />
-        {props.sidePanelProgress ? <SidePanelProgress /> : <SidePanelTree {...props} />}
-    </Grid>);
\ No newline at end of file
+        <SidePanelTree {...props} />
+    </Grid>);
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index c5cb983..c22dde2 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -43,6 +43,7 @@ import { TrashPanel } from "~/views/trash-panel/trash-panel";
 import { MainContentBar } from '~/views-components/main-content-bar/main-content-bar';
 import { Grid, LinearProgress } from '@material-ui/core';
 import { ProcessCommandDialog } from '~/views-components/process-command-dialog/process-command-dialog';
+import { isSystemWorking } from "~/store/progress-indicator/progress-indicator-reducer";
 
 type CssRules = 'root' | 'asidePanel' | 'contentWrapper' | 'content' | 'appBar';
 
@@ -50,7 +51,8 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
         overflow: 'hidden',
         width: '100vw',
-        height: '100vh'
+        height: '100vh',
+        paddingTop: theme.spacing.unit * 8
     },
     asidePanel: {
         maxWidth: '240px',
@@ -74,8 +76,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 interface WorkbenchDataProps {
     user?: User;
     currentToken?: string;
-    loadingSidePanel: boolean;
-    loadingContent: boolean;
+    working: boolean;
 }
 
 interface WorkbenchGeneralProps {
@@ -94,8 +95,7 @@ export const Workbench = withStyles(styles)(
         (state: RootState) => ({
             user: state.auth.user,
             currentToken: state.auth.apiToken,
-            loadingSidePanel: state.progressIndicator.sidePanelProgress.started,
-            loadingContent: state.progressIndicator.contentProgress.started
+            working: isSystemWorking(state.progressIndicator)
         })
     )(
         class extends React.Component<WorkbenchProps, WorkbenchState> {
@@ -105,15 +105,14 @@ export const Workbench = withStyles(styles)(
             render() {
                 const { classes } = this.props;
                 return <>
+                    <MainAppBar
+                        searchText={this.state.searchText}
+                        user={this.props.user}
+                        onSearch={this.onSearch}
+                        buildInfo={this.props.buildInfo}>
+                        {this.props.working ? <LinearProgress color="secondary" /> : null}
+                    </MainAppBar>
                     <Grid container direction="column" className={classes.root}>
-                        <Grid className={classes.appBar}>
-                            <MainAppBar
-                                searchText={this.state.searchText}
-                                user={this.props.user}
-                                onSearch={this.onSearch}
-                                buildInfo={this.props.buildInfo} />
-                        </Grid>
-                        {this.props.loadingContent || this.props.loadingSidePanel ? <LinearProgress color="secondary" /> : null}
                         {this.props.user &&
                             <Grid container item xs alignItems="stretch" wrap="nowrap">
                                 <Grid container item xs component='aside' direction='column' className={classes.asidePanel}>
diff --git a/tslint.json b/tslint.json
index 85b4369..f9b81ca 100644
--- a/tslint.json
+++ b/tslint.json
@@ -14,7 +14,8 @@
     "no-shadowed-variable": false,
     "semicolon": true,
     "array-type": false,
-    "interface-over-type-literal": false
+    "interface-over-type-literal": false,
+    "no-empty": false
   },
   "linterOptions": {
     "exclude": [
diff --git a/yarn.lock b/yarn.lock
index 3599271..45c879f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -199,6 +199,12 @@
     "@types/react" "*"
     redux "^3.6.0 || ^4.0.0"
 
+"@types/uuid at 3.4.4":
+  version "3.4.4"
+  resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.4.tgz#7af69360fa65ef0decb41fd150bf4ca5c0cefdf5"
+  dependencies:
+    "@types/node" "*"
+
 abab@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
@@ -7808,14 +7814,14 @@ utils-merge at 1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
 
+uuid at 3.3.2, uuid@^3.1.0:
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
+
 uuid@^2.0.2:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
 
-uuid@^3.1.0:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
-
 validate-npm-package-license@^3.0.1:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338"

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list