refs #master Merge branch 'origin/master' into 13986-projects-list-and-default-routing
authorDaniel Kos <daniel.kos@contractors.roche.com>
Fri, 17 Aug 2018 06:55:56 +0000 (08:55 +0200)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Fri, 17 Aug 2018 06:55:56 +0000 (08:55 +0200)
# Conflicts:
# src/store/navigation/navigation-action.ts

Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos@contractors.roche.com>

137 files changed:
.env
package.json
src/common/api/common-resource-service.test.ts
src/common/api/common-resource-service.ts
src/common/api/filter-builder.test.ts
src/common/api/filter-builder.ts
src/common/api/order-builder.test.ts
src/common/api/order-builder.ts
src/common/config.ts
src/common/labels.ts
src/common/webdav.test.ts [new file with mode: 0644]
src/common/webdav.ts [new file with mode: 0644]
src/components/collection-panel-files/collection-panel-files.tsx
src/components/column-selector/column-selector.tsx
src/components/confirmation-dialog/confirmation-dialog.tsx
src/components/context-menu/context-menu.test.tsx
src/components/data-explorer/data-explorer.tsx
src/components/data-table-filters/data-table-filters.test.tsx
src/components/data-table/data-column.ts
src/components/data-table/data-table.test.tsx
src/components/data-table/data-table.tsx
src/components/details-attribute/details-attribute.tsx
src/components/dropdown-menu/dropdown-menu.test.tsx
src/components/file-tree/file-tree-item.tsx
src/components/file-upload/file-upload.tsx
src/components/list-item-text-icon/list-item-text-icon.tsx
src/components/popover/popover.test.tsx
src/components/rename-dialog/rename-dialog.tsx
src/components/side-panel/side-panel.tsx
src/components/text-field/text-field.tsx
src/components/tree/tree.tsx
src/index.tsx
src/services/auth-service/auth-service.ts
src/services/collection-files-service/collection-files-service.ts
src/services/collection-files-service/collection-manifest-mapper.ts
src/services/collection-files-service/collection-manifest-parser.ts
src/services/collection-service/collection-service.ts
src/services/favorite-service/favorite-order-builder.ts [deleted file]
src/services/favorite-service/favorite-service.test.ts
src/services/favorite-service/favorite-service.ts
src/services/groups-service/groups-service.ts
src/services/keep-service/keep-service.ts
src/services/link-service/link-service.ts
src/services/project-service/project-service.test.ts
src/services/project-service/project-service.ts
src/services/services.ts
src/services/tag-service/tag-service.ts
src/store/auth/auth-action.ts
src/store/auth/auth-actions.test.ts
src/store/auth/auth-reducer.test.ts
src/store/auth/auth-reducer.ts
src/store/collection-panel/collection-panel-action.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.test.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-state.ts
src/store/collection-panel/collection-panel-reducer.ts
src/store/collections/creator/collection-creator-action.ts
src/store/collections/updater/collection-updater-action.ts
src/store/data-explorer/data-explorer-action.ts
src/store/data-explorer/data-explorer-middleware-service.ts
src/store/data-explorer/data-explorer-middleware.test.ts
src/store/data-explorer/data-explorer-reducer.test.tsx
src/store/data-explorer/data-explorer-reducer.ts
src/store/details-panel/details-panel-action.ts
src/store/details-panel/details-panel-reducer.ts
src/store/favorite-panel/favorite-panel-middleware-service.ts
src/store/favorites/favorites-actions.ts
src/store/navigation/navigation-action.ts
src/store/project-panel/project-panel-middleware-service.ts
src/store/project/project-action.ts
src/store/project/project-reducer.test.ts
src/store/project/project-reducer.ts
src/store/side-panel/side-panel-reducer.test.ts
src/store/side-panel/side-panel-reducer.ts
src/store/store.ts
src/store/tree-picker/tree-picker-actions.ts
src/store/tree-picker/tree-picker-reducer.test.ts
src/store/tree-picker/tree-picker-reducer.ts
src/store/tree-picker/tree-picker.ts
src/validators/create-collection/create-collection-validator.tsx [deleted file]
src/validators/create-project/create-project-validator.tsx [deleted file]
src/validators/validators.tsx
src/views-components/api-token/api-token.tsx
src/views-components/collection-panel-files/collection-panel-files.ts
src/views-components/context-menu/action-sets/collection-action-set.ts
src/views-components/context-menu/action-sets/collection-files-action-set.ts
src/views-components/context-menu/action-sets/collection-files-item-action-set.ts
src/views-components/context-menu/action-sets/collection-resource-action-set.ts
src/views-components/context-menu/action-sets/favorite-action-set.ts
src/views-components/context-menu/action-sets/project-action-set.ts
src/views-components/context-menu/action-sets/resource-action-set.ts
src/views-components/context-menu/action-sets/root-project-action-set.ts
src/views-components/context-menu/actions/favorite-action.tsx
src/views-components/context-menu/context-menu-action-set.ts
src/views-components/context-menu/context-menu.tsx
src/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected.tsx
src/views-components/create-collection-dialog/create-collection-dialog.tsx
src/views-components/create-project-dialog/create-project-dialog.tsx
src/views-components/current-token-dialog/current-token-dialog.tsx
src/views-components/data-explorer/data-explorer.tsx
src/views-components/data-explorer/renderers.tsx
src/views-components/details-panel/collection-details.tsx
src/views-components/details-panel/details-data.tsx
src/views-components/details-panel/details-panel.tsx
src/views-components/details-panel/empty-details.tsx
src/views-components/details-panel/process-details.tsx
src/views-components/details-panel/project-details.tsx
src/views-components/dialog-create/dialog-collection-create-selected.tsx
src/views-components/dialog-create/dialog-collection-create.tsx
src/views-components/dialog-create/dialog-project-create.tsx
src/views-components/dialog-update/dialog-collection-update.tsx
src/views-components/dialog-update/dialog-project-update.tsx [new file with mode: 0644]
src/views-components/favorite-star/favorite-star.tsx
src/views-components/file-remove-dialog/file-remove-dialog.ts
src/views-components/file-remove-dialog/multiple-files-remove-dialog.ts
src/views-components/main-app-bar/main-app-bar.test.tsx
src/views-components/main-app-bar/main-app-bar.tsx
src/views-components/project-tree-picker/project-tree-picker.tsx
src/views-components/project-tree/project-tree.test.tsx
src/views-components/project-tree/project-tree.tsx
src/views-components/remove-dialog/remove-dialog.tsx
src/views-components/rename-file-dialog/rename-file-dialog.tsx
src/views-components/snackbar/snackbar.tsx
src/views-components/tree-picker/tree-picker.ts
src/views-components/update-collection-dialog/update-collection-dialog..tsx
src/views-components/update-project-dialog/update-project-dialog.tsx [new file with mode: 0644]
src/views/collection-panel/collection-panel.tsx
src/views/collection-panel/collection-tag-form.tsx
src/views/favorite-panel/favorite-panel-item.ts
src/views/favorite-panel/favorite-panel.tsx
src/views/project-panel/project-panel-item.ts
src/views/project-panel/project-panel.tsx
src/views/workbench/workbench.test.tsx
src/views/workbench/workbench.tsx
tsconfig.json
yarn.lock

diff --git a/.env b/.env
index ed397c5cc0952badd931be8ccfdd7ec60d95a6d1..de1444c0bded77d30caae5614f392b9ba6b1fe12 100644 (file)
--- a/.env
+++ b/.env
@@ -4,4 +4,5 @@
 
 REACT_APP_ARVADOS_CONFIG_URL=/config.json
 REACT_APP_ARVADOS_API_HOST=qr1hi.arvadosapi.com
+REACT_APP_ARVADOS_KEEP_WEB_HOST=download.qr1hi.arvadosapi.com
 HTTPS=true
\ No newline at end of file
index cd8a9c4d241356df33f60894c8ca23f2bf0fc1e4..e2b6c4e1ec21f4017c0096a75bba61191a923dbd 100644 (file)
@@ -20,7 +20,7 @@
     "react-router": "4.3.1",
     "react-router-dom": "4.3.1",
     "react-router-redux": "5.0.0-alpha.9",
-    "react-scripts-ts": "2.16.0",
+    "react-scripts-ts": "2.17.0",
     "redux": "4.0.0",
     "redux-thunk": "2.3.0",
     "unionize": "2.1.2"
@@ -54,7 +54,9 @@
     "redux-form": "7.4.2",
     "typescript": "3.0.1"
   },
-  "moduleNameMapper": {
-    "^~/(.*)$": "<rootDir>/src/$1"
+  "jest": {
+    "moduleNameMapper": {
+      "^~/(.*)$": "<rootDir>/src/$1"
+    }
   }
 }
index d909c092aa8bad0a37ac0308a4da8ca80a872585..a1d5e0868f4e0f0c333251022aed668e2edc7ded 100644 (file)
@@ -5,7 +5,7 @@
 import { CommonResourceService } from "./common-resource-service";
 import axios, { AxiosInstance } from "axios";
 import MockAdapter from "axios-mock-adapter";
-import { Resource } from "../../models/resource";
+import { Resource } from "~/models/resource";
 
 export const mockResourceService = <R extends Resource, C extends CommonResourceService<R>>(Service: new (client: AxiosInstance) => C) => {
     const axiosInstance = axios.create();
index a836246b2e23fd50dde96eebe613f8dd14c7820f..caa4d760c9e08ef1c7af63bf86e90b34a03d4ebb 100644 (file)
@@ -3,16 +3,14 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as _ from "lodash";
-import { FilterBuilder } from "./filter-builder";
-import { OrderBuilder } from "./order-builder";
 import { AxiosInstance, AxiosPromise } from "axios";
-import { Resource } from "../../models/resource";
+import { Resource } from "~/models/resource";
 
 export interface ListArguments {
     limit?: number;
     offset?: number;
-    filters?: FilterBuilder;
-    order?: OrderBuilder;
+    filters?: string;
+    order?: string;
     select?: string[];
     distinct?: boolean;
     count?: string;
@@ -90,8 +88,8 @@ export class CommonResourceService<T extends Resource> {
         const { filters, order, ...other } = args;
         const params = {
             ...other,
-            filters: filters ? filters.serialize() : undefined,
-            order: order ? order.getOrder() : undefined
+            filters: filters ? `[${filters}]` : undefined,
+            order: order ? order : undefined
         };
         return CommonResourceService.defaultResponse(
             this.serverApi
index d129a806d0a2fd8937d05caed3cecad1b69f7d86..de2ba4cba65bbd008f581d20c4db5c16b1cbdc05 100644 (file)
@@ -9,49 +9,49 @@ describe("FilterBuilder", () => {
     let filters: FilterBuilder;
 
     beforeEach(() => {
-        filters = FilterBuilder.create();
+        filters = new FilterBuilder();
     });
 
     it("should add 'equal' rule", () => {
         expect(
-            filters.addEqual("etag", "etagValue").serialize()
-        ).toEqual(`[["etag","=","etagValue"]]`);
+            filters.addEqual("etag", "etagValue").getFilters()
+        ).toEqual(`["etag","=","etagValue"]`);
     });
 
     it("should add 'like' rule", () => {
         expect(
-            filters.addLike("etag", "etagValue").serialize()
-        ).toEqual(`[["etag","like","%etagValue%"]]`);
+            filters.addLike("etag", "etagValue").getFilters()
+        ).toEqual(`["etag","like","%etagValue%"]`);
     });
 
     it("should add 'ilike' rule", () => {
         expect(
-            filters.addILike("etag", "etagValue").serialize()
-        ).toEqual(`[["etag","ilike","%etagValue%"]]`);
+            filters.addILike("etag", "etagValue").getFilters()
+        ).toEqual(`["etag","ilike","%etagValue%"]`);
     });
 
     it("should add 'is_a' rule", () => {
         expect(
-            filters.addIsA("etag", "etagValue").serialize()
-        ).toEqual(`[["etag","is_a","etagValue"]]`);
+            filters.addIsA("etag", "etagValue").getFilters()
+        ).toEqual(`["etag","is_a","etagValue"]`);
     });
 
     it("should add 'is_a' rule for set", () => {
         expect(
-            filters.addIsA("etag", ["etagValue1", "etagValue2"]).serialize()
-        ).toEqual(`[["etag","is_a",["etagValue1","etagValue2"]]]`);
+            filters.addIsA("etag", ["etagValue1", "etagValue2"]).getFilters()
+        ).toEqual(`["etag","is_a",["etagValue1","etagValue2"]]`);
     });
 
     it("should add 'in' rule", () => {
         expect(
-            filters.addIn("etag", "etagValue").serialize()
-        ).toEqual(`[["etag","in","etagValue"]]`);
+            filters.addIn("etag", "etagValue").getFilters()
+        ).toEqual(`["etag","in","etagValue"]`);
     });
 
     it("should add 'in' rule for set", () => {
         expect(
-            filters.addIn("etag", ["etagValue1", "etagValue2"]).serialize()
-        ).toEqual(`[["etag","in",["etagValue1","etagValue2"]]]`);
+            filters.addIn("etag", ["etagValue1", "etagValue2"]).getFilters()
+        ).toEqual(`["etag","in",["etagValue1","etagValue2"]]`);
     });
 
     it("should add multiple rules", () => {
@@ -59,28 +59,14 @@ describe("FilterBuilder", () => {
             filters
                 .addIn("etag", ["etagValue1", "etagValue2"])
                 .addEqual("href", "hrefValue")
-                .serialize()
-        ).toEqual(`[["etag","in",["etagValue1","etagValue2"]],["href","=","hrefValue"]]`);
-    });
-
-    it("should concatenate multiple builders", () => {
-        expect(
-            filters
-                .concat(FilterBuilder.create().addIn("etag", ["etagValue1", "etagValue2"]))
-                .concat(FilterBuilder.create().addEqual("href", "hrefValue"))
-                .serialize()
-        ).toEqual(`[["etag","in",["etagValue1","etagValue2"]],["href","=","hrefValue"]]`);
+                .getFilters()
+        ).toEqual(`["etag","in",["etagValue1","etagValue2"]],["href","=","hrefValue"]`);
     });
 
     it("should add attribute prefix", () => {
-        expect(FilterBuilder
-            .create("myPrefix")
-            .addIn("etag", ["etagValue1", "etagValue2"])
-            .serialize())
-            .toEqual(`[["my_prefix.etag","in",["etagValue1","etagValue2"]]]`);
+        expect(new FilterBuilder()
+            .addIn("etag", ["etagValue1", "etagValue2"], "myPrefix")
+            .getFilters())
+            .toEqual(`["my_prefix.etag","in",["etagValue1","etagValue2"]]`);
     });
-
-
-
-
 });
index e5aab3ac72825c37d0b503287bf07968eb93dc1d..b5558dbb16a291f8ecb81fdbd642f742bb99b9d3 100644 (file)
@@ -4,58 +4,48 @@
 
 import * as _ from "lodash";
 
-export class FilterBuilder {
-    static create(resourcePrefix = "") {
-        return new FilterBuilder(resourcePrefix);
-    }
-
-    constructor(
-        private resourcePrefix = "",
-        private filters = "") { }
+export function joinFilters(filters0?: string, filters1?: string) {
+    return [filters0, filters1].filter(s => s).join(",");
+}
 
-    public addEqual(field: string, value?: string) {
-        return this.addCondition(field, "=", value);
-    }
+export class FilterBuilder {
+    constructor(private filters = "") { }
 
-    public addLike(field: string, value?: string) {
-        return this.addCondition(field, "like", value, "%", "%");
+    public addEqual(field: string, value?: string, resourcePrefix?: string) {
+        return this.addCondition(field, "=", value, "", "", resourcePrefix );
     }
 
-    public addILike(field: string, value?: string) {
-        return this.addCondition(field, "ilike", value, "%", "%");
+    public addLike(field: string, value?: string, resourcePrefix?: string) {
+        return this.addCondition(field, "like", value, "%", "%", resourcePrefix);
     }
 
-    public addIsA(field: string, value?: string | string[]) {
-        return this.addCondition(field, "is_a", value);
+    public addILike(field: string, value?: string, resourcePrefix?: string) {
+        return this.addCondition(field, "ilike", value, "%", "%", resourcePrefix);
     }
 
-    public addIn(field: string, value?: string | string[]) {
-        return this.addCondition(field, "in", value);
+    public addIsA(field: string, value?: string | string[], resourcePrefix?: string) {
+        return this.addCondition(field, "is_a", value, "", "", resourcePrefix);
     }
 
-    public concat(filterBuilder: FilterBuilder) {
-        return new FilterBuilder(this.resourcePrefix, this.filters + (this.filters && filterBuilder.filters ? "," : "") + filterBuilder.getFilters());
+    public addIn(field: string, value?: string | string[], resourcePrefix?: string) {
+        return this.addCondition(field, "in", value, "", "", resourcePrefix);
     }
 
     public getFilters() {
         return this.filters;
     }
 
-    public serialize() {
-        return "[" + this.filters + "]";
-    }
-
-    private addCondition(field: string, cond: string, value?: string | string[], prefix: string = "", postfix: string = "") {
+    private addCondition(field: string, cond: string, value?: string | string[], prefix: string = "", postfix: string = "", resourcePrefix?: string) {
         if (value) {
             value = typeof value === "string"
                 ? `"${prefix}${value}${postfix}"`
                 : `["${value.join(`","`)}"]`;
 
-            const resourcePrefix = this.resourcePrefix
-                ? _.snakeCase(this.resourcePrefix) + "."
+            const resPrefix = resourcePrefix
+                ? _.snakeCase(resourcePrefix) + "."
                 : "";
 
-            this.filters += `${this.filters ? "," : ""}["${resourcePrefix}${_.snakeCase(field.toString())}","${cond}",${value}]`;
+            this.filters += `${this.filters ? "," : ""}["${resPrefix}${_.snakeCase(field)}","${cond}",${value}]`;
         }
         return this;
     }
index f53bddb5cc51e047540029564579041c4798c647..56b66f3bdd461459bf0b3d61ae4b87b45ef71ea8 100644 (file)
@@ -6,22 +6,10 @@ import { OrderBuilder } from "./order-builder";
 
 describe("OrderBuilder", () => {
     it("should build correct order query", () => {
-        const order = OrderBuilder
-            .create()
+        const order = new OrderBuilder()
             .addAsc("kind")
             .addDesc("modifiedAt")
             .getOrder();
         expect(order).toEqual(["kind asc", "modified_at desc"]);
     });
-
-    it("should combine results with other builder", () => {
-        const order = OrderBuilder
-            .create()
-            .addAsc("kind")
-            .concat(OrderBuilder
-                .create("properties")
-                .addDesc("modifiedAt"))
-            .getOrder();
-        expect(order).toEqual(["kind asc", "properties.modified_at desc"]);
-    });
 });
index ed990541c61bb960e4b1a5074a4530570d1617eb..196b06952e55c911d9cd0bac6ed881151918bf98 100644 (file)
@@ -3,40 +3,28 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as _ from "lodash";
-import { Resource } from "../../models/resource";
+import { Resource } from "~/models/resource";
 
-export class OrderBuilder<T extends Resource = Resource> {
-
-    static create<T extends Resource = Resource>(prefix?: string){
-        return new OrderBuilder<T>([], prefix);
-    }
+export enum OrderDirection { ASC, DESC }
 
-    private constructor(
-        private order: string[] = [],
-        private prefix = ""){}
+export class OrderBuilder<T extends Resource = Resource> {
 
-    private addRule (direction: string, attribute: keyof T) {
-        const prefix = this.prefix ? this.prefix + "." : "";
-        const order = [...this.order, `${prefix}${_.snakeCase(attribute.toString())} ${direction}`];
-        return new OrderBuilder<T>(order, prefix);
-    }
+    constructor(private order: string[] = []) {}
 
-    addAsc(attribute: keyof T) {
-        return this.addRule("asc", attribute);
+    addOrder(direction: OrderDirection, attribute: keyof T, prefix?: string) {
+        this.order.push(`${prefix ? prefix + "." : ""}${_.snakeCase(attribute.toString())} ${direction === OrderDirection.ASC ? "asc" : "desc"}`);
+        return this;
     }
 
-    addDesc(attribute: keyof T) {
-        return this.addRule("desc", attribute);
+    addAsc(attribute: keyof T, prefix?: string) {
+        return this.addOrder(OrderDirection.ASC, attribute, prefix);
     }
 
-    concat(orderBuilder: OrderBuilder){
-        return new OrderBuilder<T>(
-            this.order.concat(orderBuilder.getOrder()),
-            this.prefix
-        );
+    addDesc(attribute: keyof T, prefix?: string) {
+        return this.addOrder(OrderDirection.DESC, attribute, prefix);
     }
 
     getOrder() {
-        return this.order.slice();
+        return this.order.join(",");
     }
 }
index 250c806c69641cbf9a63cde243075bd234a88177..759a20158f9f8cafac459cd4ee2213f36c00890d 100644 (file)
@@ -7,24 +7,31 @@ import Axios from "axios";
 export const CONFIG_URL = process.env.REACT_APP_ARVADOS_CONFIG_URL || "/config.json";
 
 export interface Config {
-    API_HOST: string;
+    apiHost: string;
+    keepWebHost: string;
 }
 
 export const fetchConfig = () => {
     return Axios
-        .get<Config>(CONFIG_URL + "?nocache=" + (new Date()).getTime())
+        .get<ConfigJSON>(CONFIG_URL + "?nocache=" + (new Date()).getTime())
         .then(response => response.data)
         .catch(() => Promise.resolve(getDefaultConfig()))
         .then(mapConfig);
 };
 
-const mapConfig = (config: Config): Config => ({
-    ...config,
-    API_HOST: addProtocol(config.API_HOST)
+interface ConfigJSON {
+    API_HOST: string;
+    KEEP_WEB_HOST: string;
+}
+
+const mapConfig = (config: ConfigJSON): Config => ({
+    apiHost: addProtocol(config.API_HOST),
+    keepWebHost: addProtocol(config.KEEP_WEB_HOST)
 });
 
-const getDefaultConfig = (): Config => ({
-    API_HOST: process.env.REACT_APP_ARVADOS_API_HOST || ""
+const getDefaultConfig = (): ConfigJSON => ({
+    API_HOST: process.env.REACT_APP_ARVADOS_API_HOST || "",
+    KEEP_WEB_HOST: process.env.REACT_APP_ARVADOS_KEEP_WEB_HOST || ""
 });
 
 const addProtocol = (url: string) => `${window.location.protocol}//${url}`;
index ed3a43d1427e6622e55c9059cddc7bfbf77eff46..0e3131db6a3f8c9621c05e3e4df3770960537b88 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ResourceKind } from "../models/resource";
+import { ResourceKind } from "~/models/resource";
 
 export const resourceLabel = (type: string) => {
     switch (type) {
@@ -15,4 +15,4 @@ export const resourceLabel = (type: string) => {
         default:
             return "Unknown";
     }
-};
\ No newline at end of file
+};
diff --git a/src/common/webdav.test.ts b/src/common/webdav.test.ts
new file mode 100644 (file)
index 0000000..d96465b
--- /dev/null
@@ -0,0 +1,131 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { WebDAV } from "./webdav";
+
+describe('WebDAV', () => {
+    it('makes use of provided config', async () => {
+        const { open, load, setRequestHeader, createRequest } = mockCreateRequest();
+        const webdav = new WebDAV({ baseURL: 'http://foo.com/', headers: { Authorization: 'Basic' } }, createRequest);
+        const promise = webdav.propfind('foo');
+        load();
+        const request = await promise;
+        expect(open).toHaveBeenCalledWith('PROPFIND', 'http://foo.com/foo');
+        expect(setRequestHeader).toHaveBeenCalledWith('Authorization', 'Basic');
+        expect(request).toBeInstanceOf(XMLHttpRequest);
+    });
+
+    it('allows to modify defaults after instantiation', async () => {
+        const { open, load, setRequestHeader, createRequest } = mockCreateRequest();
+        const webdav = new WebDAV(undefined, createRequest);
+        webdav.defaults.baseURL = 'http://foo.com/';
+        webdav.defaults.headers = { Authorization: 'Basic' };
+        const promise = webdav.propfind('foo');
+        load();
+        const request = await promise;
+        expect(open).toHaveBeenCalledWith('PROPFIND', 'http://foo.com/foo');
+        expect(setRequestHeader).toHaveBeenCalledWith('Authorization', 'Basic');
+        expect(request).toBeInstanceOf(XMLHttpRequest);
+    });
+
+    it('PROPFIND', async () => {
+        const { open, load, createRequest } = mockCreateRequest();
+        const webdav = new WebDAV(undefined, createRequest);
+        const promise = webdav.propfind('foo');
+        load();
+        const request = await promise;
+        expect(open).toHaveBeenCalledWith('PROPFIND', 'foo');
+        expect(request).toBeInstanceOf(XMLHttpRequest);
+    });
+
+    it('PUT', async () => {
+        const { open, send, load, progress, createRequest } = mockCreateRequest();
+        const onProgress = jest.fn();
+        const webdav = new WebDAV(undefined, createRequest);
+        const promise = webdav.put('foo', 'Test data', { onProgress });
+        progress();
+        load();
+        const request = await promise;
+        expect(open).toHaveBeenCalledWith('PUT', 'foo');
+        expect(send).toHaveBeenCalledWith('Test data');
+        expect(onProgress).toHaveBeenCalled();
+        expect(request).toBeInstanceOf(XMLHttpRequest);
+    });
+
+    it('COPY', async () => {
+        const { open, setRequestHeader, load, createRequest } = mockCreateRequest();
+        const webdav = new WebDAV(undefined, createRequest);
+        const promise = webdav.copy('foo', 'foo-copy');
+        load();
+        const request = await promise;
+        expect(open).toHaveBeenCalledWith('COPY', 'foo');
+        expect(setRequestHeader).toHaveBeenCalledWith('Destination', 'foo-copy');
+        expect(request).toBeInstanceOf(XMLHttpRequest);
+    });
+
+    it('COPY - adds baseURL to Destination header', async () => {
+        const { open, setRequestHeader, load, createRequest } = mockCreateRequest();
+        const webdav = new WebDAV(undefined, createRequest);
+        webdav.defaults.baseURL = 'base/';
+        const promise = webdav.copy('foo', 'foo-copy');
+        load();
+        const request = await promise;
+        expect(open).toHaveBeenCalledWith('COPY', 'base/foo');
+        expect(setRequestHeader).toHaveBeenCalledWith('Destination', 'base/foo-copy');
+        expect(request).toBeInstanceOf(XMLHttpRequest);
+    });
+
+    it('MOVE', async () => {
+        const { open, setRequestHeader, load, createRequest } = mockCreateRequest();
+        const webdav = new WebDAV(undefined, createRequest);
+        const promise = webdav.move('foo', 'foo-copy');
+        load();
+        const request = await promise;
+        expect(open).toHaveBeenCalledWith('MOVE', 'foo');
+        expect(setRequestHeader).toHaveBeenCalledWith('Destination', 'foo-copy');
+        expect(request).toBeInstanceOf(XMLHttpRequest);
+    });
+
+    it('MOVE - adds baseURL to Destination header', async () => {
+        const { open, setRequestHeader, load, createRequest } = mockCreateRequest();
+        const webdav = new WebDAV(undefined, createRequest);
+        webdav.defaults.baseURL = 'base/';
+        const promise = webdav.move('foo', 'foo-moved');
+        load();
+        const request = await promise;
+        expect(open).toHaveBeenCalledWith('MOVE', 'base/foo');
+        expect(setRequestHeader).toHaveBeenCalledWith('Destination', 'base/foo-moved');
+        expect(request).toBeInstanceOf(XMLHttpRequest);
+    });
+
+    it('DELETE', async () => {
+        const { open, load, createRequest } = mockCreateRequest();
+        const webdav = new WebDAV(undefined, createRequest);
+        const promise = webdav.delete('foo');
+        load();
+        const request = await promise;
+        expect(open).toHaveBeenCalledWith('DELETE', 'foo');
+        expect(request).toBeInstanceOf(XMLHttpRequest);
+    });
+});
+
+const mockCreateRequest = () => {
+    const send = jest.fn();
+    const open = jest.fn();
+    const setRequestHeader = jest.fn();
+    const request = new XMLHttpRequest();
+    request.send = send;
+    request.open = open;
+    request.setRequestHeader = setRequestHeader;
+    const load = () => request.dispatchEvent(new Event('load'));
+    const progress = () => request.dispatchEvent(new Event('progress'));
+    return {
+        send,
+        open,
+        load,
+        progress,
+        setRequestHeader,
+        createRequest: () => request
+    };
+};
diff --git a/src/common/webdav.ts b/src/common/webdav.ts
new file mode 100644 (file)
index 0000000..57caebc
--- /dev/null
@@ -0,0 +1,90 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export class WebDAV {
+
+    defaults: WebDAVDefaults = {
+        baseURL: '',
+        headers: {},
+    };
+
+    constructor(config?: Partial<WebDAVDefaults>, private createRequest = () => new XMLHttpRequest()) {
+        if (config) {
+            this.defaults = { ...this.defaults, ...config };
+        }
+    }
+
+    propfind = (url: string, config: WebDAVRequestConfig = {}) =>
+        this.request({
+            ...config, url,
+            method: 'PROPFIND'
+        })
+
+    put = (url: string, data?: any, config: WebDAVRequestConfig = {}) =>
+        this.request({
+            ...config, url,
+            method: 'PUT',
+            data,
+        })
+
+    copy = (url: string, destination: string, config: WebDAVRequestConfig = {}) =>
+        this.request({
+            ...config, url,
+            method: 'COPY',
+            headers: { ...config.headers, Destination: this.defaults.baseURL + destination }
+        })
+
+    move = (url: string, destination: string, config: WebDAVRequestConfig = {}) =>
+        this.request({
+            ...config, url,
+            method: 'MOVE',
+            headers: { ...config.headers, Destination: this.defaults.baseURL + destination }
+        })
+
+    delete = (url: string, config: WebDAVRequestConfig = {}) =>
+        this.request({
+            ...config, url,
+            method: 'DELETE'
+        })
+
+    private request = (config: RequestConfig) => {
+        return new Promise<XMLHttpRequest>((resolve, reject) => {
+            const r = this.createRequest();
+            r.open(config.method, this.defaults.baseURL + config.url);
+
+            const headers = { ...this.defaults.headers, ...config.headers };
+            Object
+                .keys(headers)
+                .forEach(key => r.setRequestHeader(key, headers[key]));
+
+            if (config.onProgress) {
+                r.addEventListener('progress', config.onProgress);
+            }
+
+            r.addEventListener('load', () => resolve(r));
+            r.addEventListener('error', () => reject(r));
+
+            r.send(config.data);
+        });
+    }
+}
+export interface WebDAVRequestConfig {
+    headers?: {
+        [key: string]: string;
+    };
+    onProgress?: (event: ProgressEvent) => void;
+}
+
+interface WebDAVDefaults {
+    baseURL: string;
+    headers: { [key: string]: string };
+}
+
+interface RequestConfig {
+    method: string;
+    url: string;
+    headers?: { [key: string]: string };
+    data?: any;
+    onProgress?: (event: ProgressEvent) => void;
+}
index afe9e8517cc4027d5e90a4fca4d6007a560ea5d1..665758c3e62cbd916366045f79c1e4026a560c99 100644 (file)
@@ -6,12 +6,12 @@ import * as React from 'react';
 import { TreeItem, TreeItemStatus } from '../tree/tree';
 import { FileTreeData } from '../file-tree/file-tree-data';
 import { FileTree } from '../file-tree/file-tree';
-import { IconButton, Grid, Typography, StyleRulesCallback, withStyles, WithStyles, CardHeader, CardContent, Card, Button } from '@material-ui/core';
+import { IconButton, Grid, Typography, StyleRulesCallback, withStyles, WithStyles, CardHeader, Card, Button } from '@material-ui/core';
 import { CustomizeTableIcon } from '../icon/icon';
 import { connect, DispatchProp } from "react-redux";
 import { Dispatch } from "redux";
-import { RootState } from "../../store/store";
-import { ServiceRepository } from "../../services/services";
+import { RootState } from "~/store/store";
+import { ServiceRepository } from "~/services/services";
 
 export interface CollectionPanelFilesProps {
     items: Array<TreeItem<FileTreeData>>;
index f2e42dd251bfbfa07e3c926de0a24cd7291c2038..5e4b3397fab9e3b695d4c92bb37ad1e5e99c60b9 100644 (file)
@@ -9,7 +9,7 @@ import { DataColumn } from '../data-table/data-column';
 import { Popover } from "../popover/popover";
 import { IconButtonProps } from '@material-ui/core/IconButton';
 import { DataColumns } from '../data-table/data-table';
-import { ArvadosTheme } from "../../common/custom-theme";
+import { ArvadosTheme } from "~/common/custom-theme";
 
 interface ColumnSelectorDataProps {
     columns: DataColumns<any>;
index 6e87416781eb01a02866bc9f2bcea65dc0c59a68..9ca3454a61e7c304ef703f32f84ef734662837f6 100644 (file)
@@ -3,10 +3,8 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from "react";
-import { defaultTo, property } from 'lodash';
-import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography, DialogContentText, CircularProgress } from "@material-ui/core";
+import { Dialog, DialogTitle, DialogContent, DialogActions, Button, DialogContentText } from "@material-ui/core";
 import { WithDialogProps } from "../../store/dialog/with-dialog";
-import { TextField } from "../text-field/text-field";
 
 export interface ConfirmationDialogDataProps {
     title: string;
index 5ced213a3fb69435f42b1d3a8ba9bc9e93610df9..faf05f1ff32984d3b663ce5eaa296e0fdd28a306 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from "react";
-import { mount, configure, shallow } from "enzyme";
+import { mount, configure } from "enzyme";
 import * as Adapter from "enzyme-adapter-react-16";
 import { ContextMenu } from "./context-menu";
 import { ListItem } from "@material-ui/core";
index 7acc1a835ac2997eddff27220f461318bcfc7f19..681caa9478c15d194c0ab1f904a59369b015089a 100644 (file)
@@ -7,10 +7,10 @@ import { Grid, Paper, Toolbar, StyleRulesCallback, withStyles, WithStyles, Table
 import MoreVertIcon from "@material-ui/icons/MoreVert";
 import { ColumnSelector } from "../column-selector/column-selector";
 import { DataTable, DataColumns } from "../data-table/data-table";
-import { DataColumn } from "../data-table/data-column";
+import { DataColumn, SortDirection } from "../data-table/data-column";
 import { DataTableFilterItem } from '../data-table-filters/data-table-filters';
 import { SearchInput } from '../search-input/search-input';
-import { ArvadosTheme } from "../../common/custom-theme";
+import { ArvadosTheme } from "~/common/custom-theme";
 import { DefaultView } from '../default-view/default-view';
 import { IconType } from '../icon/icon';
 
@@ -68,10 +68,10 @@ type DataExplorerProps<T> = DataExplorerDataProps<T> & DataExplorerActionProps<T
 export const DataExplorer = withStyles(styles)(
     class DataExplorerGeneric<T> extends React.Component<DataExplorerProps<T>> {
         render() {
-            const { 
-                columns, onContextMenu, onFiltersChange, onSortToggle, extractKey, 
-                rowsPerPage, rowsPerPageOptions, onColumnToggle, searchValue, onSearch, 
-                items, itemsAvailable, onRowClick, onRowDoubleClick, defaultIcon, defaultMessages, classes 
+            const {
+                columns, onContextMenu, onFiltersChange, onSortToggle, extractKey,
+                rowsPerPage, rowsPerPageOptions, onColumnToggle, searchValue, onSearch,
+                items, itemsAvailable, onRowClick, onRowDoubleClick, defaultIcon, defaultMessages, classes
             } = this.props;
             return <div>
                 { items.length > 0 ? (
@@ -111,7 +111,7 @@ export const DataExplorer = withStyles(styles)(
                         </Toolbar>
                     </Paper>
                 ) : (
-                    <DefaultView 
+                    <DefaultView
                         classRoot={classes.defaultRoot}
                         icon={defaultIcon}
                         classIcon={classes.defaultIcon}
@@ -140,6 +140,8 @@ export const DataExplorer = withStyles(styles)(
             name: "Actions",
             selected: true,
             configurable: false,
+            sortDirection: SortDirection.NONE,
+            filters: [],
             key: "context-actions",
             render: this.renderContextMenuTrigger,
             width: "auto"
index b0a84b51760a50cef284c567bd24ddd1754a61ea..9a3fa4dc5ca4640e294871c6f389baeb691abf1e 100644 (file)
@@ -4,7 +4,7 @@
 
 import * as React from "react";
 import { mount, configure } from "enzyme";
-import { DataTableFilters, DataTableFilterItem } from "./data-table-filters";
+import { DataTableFilters } from "./data-table-filters";
 import * as Adapter from 'enzyme-adapter-react-16';
 import { Checkbox, ButtonBase, ListItem, Button, ListItemText } from "@material-ui/core";
 
index a5000b935bcea4b1420b14408516d03718dacb67..d4e23ab5b5eb95afc9f68414639e8348b235f545 100644 (file)
@@ -3,14 +3,15 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { DataTableFilterItem } from "../data-table-filters/data-table-filters";
+import * as React from "react";
 
 export interface DataColumn<T, F extends DataTableFilterItem = DataTableFilterItem> {
+    key?: React.Key;
     name: string;
     selected: boolean;
     configurable: boolean;
-    key?: React.Key;
-    sortDirection?: SortDirection;
-    filters?: F[];
+    sortDirection: SortDirection;
+    filters: F[];
     render: (item: T) => React.ReactElement<any>;
     renderHeader?: () => React.ReactElement<any>;
     width?: string;
index 99ed2daffb52ab5c1c0cf051205eb5ac28e96806..77c7825bc135079dd85bacce2beb42ddd728b1d7 100644 (file)
@@ -18,17 +18,20 @@ describe("<DataTable />", () => {
             {
                 name: "Column 1",
                 render: () => <span />,
-                selected: true
+                selected: true,
+                configurable: true
             },
             {
                 name: "Column 2",
                 render: () => <span />,
-                selected: true
+                selected: true,
+                configurable: true
             },
             {
                 name: "Column 3",
                 render: () => <span />,
-                selected: false
+                selected: false,
+                configurable: true
             }
         ];
         const dataTable = mount(<DataTable
@@ -47,7 +50,8 @@ describe("<DataTable />", () => {
             {
                 name: "Column 1",
                 render: () => <span />,
-                selected: true
+                selected: true,
+                configurable: true
             }
         ];
         const dataTable = mount(<DataTable
@@ -67,7 +71,8 @@ describe("<DataTable />", () => {
                 name: "Column 1",
                 renderHeader: () => <span>Column Header</span>,
                 render: () => <span />,
-                selected: true
+                selected: true,
+                configurable: true
             }
         ];
         const dataTable = mount(<DataTable
@@ -87,7 +92,8 @@ describe("<DataTable />", () => {
                 name: "Column 1",
                 key: "column-1-key",
                 render: () => <span />,
-                selected: true
+                selected: true,
+                configurable: true
             }
         ];
         const dataTable = mount(<DataTable
@@ -107,12 +113,14 @@ describe("<DataTable />", () => {
             {
                 name: "Column 1",
                 render: (item) => <Typography>{item}</Typography>,
-                selected: true
+                selected: true,
+                configurable: true
             },
             {
                 name: "Column 2",
                 render: (item) => <Button>{item}</Button>,
-                selected: true
+                selected: true,
+                configurable: true
             }
         ];
         const dataTable = mount(<DataTable
@@ -132,6 +140,7 @@ describe("<DataTable />", () => {
             name: "Column 1",
             sortDirection: SortDirection.ASC,
             selected: true,
+            configurable: true,
             render: (item) => <Typography>{item}</Typography>
         }];
         const onSortToggle = jest.fn();
@@ -153,6 +162,7 @@ describe("<DataTable />", () => {
             name: "Column 1",
             sortDirection: SortDirection.ASC,
             selected: true,
+            configurable: true,
             filters: [{ name: "Filter 1", selected: true }],
             render: (item) => <Typography>{item}</Typography>
         }];
index f92f97b72f94b74d7da7dc23ff175ceeec225829..34f8168a55b50fc206829114846b46d8a51ff323 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { Table, TableBody, TableRow, TableCell, TableHead, TableSortLabel, StyleRulesCallback, Theme, WithStyles, withStyles, Typography } from '@material-ui/core';
+import { Table, TableBody, TableRow, TableCell, TableHead, TableSortLabel, StyleRulesCallback, Theme, WithStyles, withStyles } from '@material-ui/core';
 import { DataColumn, SortDirection } from './data-column';
 import { DataTableFilters,  DataTableFilterItem } from "../data-table-filters/data-table-filters";
 
index 9e58a3e4a0a12f1d8e3ac5eac54f7fb169729129..3888b04b67d596ea84f3e11cbfeba30999adbc03 100644 (file)
@@ -5,7 +5,7 @@
 import * as React from 'react';
 import Typography from '@material-ui/core/Typography';
 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
-import { ArvadosTheme } from '../../common/custom-theme';
+import { ArvadosTheme } from '~/common/custom-theme';
 import * as classnames from "classnames";
 
 type CssRules = 'attribute' | 'label' | 'value' | 'link';
index da232bd288499810c827f7ac602796348758f3fe..32039939031290d018a34855db76d17664f7ec9f 100644 (file)
@@ -30,13 +30,13 @@ describe("<DropdownMenu />", () => {
     it("opens on menu icon click", () => {
         const dropdownMenu = shallow(<DropdownMenu id="test-menu" icon={<PaginationRightArrowIcon />} />);
         dropdownMenu.find(IconButton).simulate("click", {currentTarget: {}});
-        expect(dropdownMenu.state().anchorEl).toBeDefined();
+        expect((dropdownMenu.state() as any).anchorEl).toBeDefined();
     });
 
     it("closes on menu click", () => {
         const dropdownMenu = shallow(<DropdownMenu id="test-menu" icon={<PaginationRightArrowIcon />} />);
         dropdownMenu.find(Menu).simulate("click", {currentTarget: {}});
-        expect(dropdownMenu.state().anchorEl).toBeUndefined();
+        expect((dropdownMenu.state() as any).anchorEl).toBeUndefined();
     });
 
 });
index 5255ded1febb95b074e72c5e73f3d19ef55d0480..e2d6b26cb4117e913c396e5cb105bbb8dbc7a3e8 100644 (file)
@@ -6,7 +6,7 @@ import * as React from "react";
 import { TreeItem } from "../tree/tree";
 import { ProjectIcon, MoreOptionsIcon, DefaultIcon, CollectionIcon } from "../icon/icon";
 import { Typography, IconButton, StyleRulesCallback, withStyles, WithStyles } from "@material-ui/core";
-import { formatFileSize } from "../../common/formatters";
+import { formatFileSize } from "~/common/formatters";
 import { ListItemTextIcon } from "../list-item-text-icon/list-item-text-icon";
 import { FileTreeData } from "./file-tree-data";
 
index ec4fdc2070bbc0c93578253d849dd2dbd5bfa334..74efe009e9503a95576993985c776f9f2b396282 100644 (file)
@@ -13,8 +13,8 @@ import {
 import { withStyles } from '@material-ui/core';
 import Dropzone from 'react-dropzone';
 import { CloudUploadIcon } from "../icon/icon";
-import { formatFileSize, formatProgress, formatUploadSpeed } from "../../common/formatters";
-import { UploadFile } from "../../store/collections/uploader/collection-uploader-actions";
+import { formatFileSize, formatProgress, formatUploadSpeed } from "~/common/formatters";
+import { UploadFile } from "~/store/collections/uploader/collection-uploader-actions";
 
 type CssRules = "root" | "dropzone" | "container" | "uploadIcon";
 
index 6f8a2c4302e635bd9b4222801d0ddbb9ce5f82bb..b34c6ab5f9cab8784e525b6356963f4fad4b800c 100644 (file)
@@ -4,7 +4,7 @@
 
 import * as React from 'react';
 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
-import { ArvadosTheme } from '../../common/custom-theme';
+import { ArvadosTheme } from '~/common/custom-theme';
 import { ListItemIcon, ListItemText, Typography } from '@material-ui/core';
 import { IconType } from '../icon/icon';
 import * as classnames from "classnames";
index 37007ab41eb4fd397b1653e29b2d0281b10e1e01..4bc2ebd49b073c2068408682ecd7a3098dbc1267 100644 (file)
@@ -15,7 +15,7 @@ describe("<Popover />", () => {
     it("opens on default trigger click", () => {
         const popover = mount(<Popover />);
         popover.find(DefaultTrigger).simulate("click");
-        expect(popover.state().anchorEl).toBeDefined();
+        expect((popover.state() as any).anchorEl).toBeDefined();
     });
 
     it("renders custom trigger", () => {
@@ -26,7 +26,7 @@ describe("<Popover />", () => {
     it("opens on custom trigger click", () => {
         const popover = mount(<Popover triggerComponent={CustomTrigger} />);
         popover.find(CustomTrigger).simulate("click");
-        expect(popover.state().anchorEl).toBeDefined();
+        expect((popover.state() as any).anchorEl).toBeDefined();
     });
 
     it("renders children when opened", () => {
@@ -47,7 +47,7 @@ describe("<Popover />", () => {
         );
         popover.find(DefaultTrigger).simulate("click");
         popover.find(CustomTrigger).simulate("click");
-        expect(popover.state().anchorEl).toBeDefined();
+        expect((popover.state() as any).anchorEl).toBeDefined();
     });
     it("closes on content click if closeOnContentClick is set", () => {
         const popover = mount(
@@ -57,7 +57,7 @@ describe("<Popover />", () => {
         );
         popover.find(DefaultTrigger).simulate("click");
         popover.find(CustomTrigger).simulate("click");
-        expect(popover.state().anchorEl).toBeUndefined();
+        expect((popover.state() as any).anchorEl).toBeUndefined();
     });
 
 });
index f70d857df36c036c8aa71a0e20e8dfad48f1c674..e025764848de03adae00bc14227764acff00c58b 100644 (file)
@@ -4,8 +4,8 @@
 
 import * as React from "react";
 import { InjectedFormProps, Field } from "redux-form";
-import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography, DialogContentText, CircularProgress } from "@material-ui/core";
-import { WithDialogProps } from "../../store/dialog/with-dialog";
+import { Dialog, DialogTitle, DialogContent, DialogActions, Button, DialogContentText, CircularProgress } from "@material-ui/core";
+import { WithDialogProps } from "~/store/dialog/with-dialog";
 import { TextField } from "../text-field/text-field";
 
 export const RenameDialog = (props: WithDialogProps<string> & InjectedFormProps<{ name: string }>) =>
index c7649d4d8082edf81ac8d3a4235d035c72812620..206cb6322b84a1d181d451f76d8886328837a969 100644 (file)
@@ -5,7 +5,7 @@
 import * as React from 'react';
 import { ReactElement } from 'react';
 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
-import { ArvadosTheme } from '../../common/custom-theme';
+import { ArvadosTheme } from '~/common/custom-theme';
 import { List, ListItem, ListItemIcon, Collapse } from "@material-ui/core";
 import { SidePanelRightArrowIcon, IconType } from '../icon/icon';
 import * as classnames from "classnames";
index d9f11f43322a5a9dcfe70e0e5a59692592c08c39..1fe77ca66c6de47c4516352cc54d9dfcfa4e659e 100644 (file)
@@ -4,7 +4,7 @@
 
 import * as React from 'react';
 import { WrappedFieldProps } from 'redux-form';
-import { ArvadosTheme } from '../../common/custom-theme';
+import { ArvadosTheme } from '~/common/custom-theme';
 import { TextField as MaterialTextField, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
 
 type CssRules = 'textField';
@@ -25,4 +25,4 @@ export const TextField = withStyles(styles)((props: WrappedFieldProps & WithStyl
         autoComplete='off'
         fullWidth={true}
         {...props.input}
-    />);
\ No newline at end of file
+    />);
index 669b70c0e855c1d551bc4a4dedfeacbe35000814..3e8cf904cfaae2274d1273bb81f1f77048b34c2a 100644 (file)
@@ -9,7 +9,7 @@ import { ReactElement } from "react";
 import CircularProgress from '@material-ui/core/CircularProgress';
 import * as classnames from "classnames";
 
-import { ArvadosTheme } from '../../common/custom-theme';
+import { ArvadosTheme } from '~/common/custom-theme';
 import { SidePanelRightArrowIcon } from '../icon/icon';
 
 type CssRules = 'list'
index dd2722335d091f032bcd116979df33df61a37953..cfdbb46cf55882743fd714e7520a9c64884a8d22 100644 (file)
@@ -40,12 +40,12 @@ addMenuActionSet(ContextMenuKind.COLLECTION_RESOURCE, collectionResourceActionSe
 fetchConfig()
     .then(config => {
         const history = createBrowserHistory();
-        const services = createServices(config.API_HOST);
+        const services = createServices(config);
         const store = configureStore(history, services);
 
         store.dispatch(initAuth());
         store.dispatch(getProjectList(services.authService.getUuid()));
-
+        
         const TokenComponent = (props: any) => <ApiToken authService={services.authService} {...props}/>;
         const WorkbenchComponent = (props: any) => <Workbench authService={services.authService} {...props}/>;
 
index dd47662a5c86a82b84335f905fe05744e81bff2b..57915f70578f04be4afd19ef8d6de2543b1cdf3b 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { User } from "../../models/user";
+import { User } from "~/models/user";
 import { AxiosInstance } from "axios";
 
 export const API_TOKEN_KEY = 'apiToken';
index 5e6891c8ed417254f451cd2e640bdf80ca510222..1d9a537f4a559cfdf06ab01d29eb9a011943f1ed 100644 (file)
@@ -5,7 +5,7 @@
 import { CollectionService } from "../collection-service/collection-service";
 import { parseKeepManifestText, stringifyKeepManifest } from "./collection-manifest-parser";
 import { mapManifestToCollectionFilesTree } from "./collection-manifest-mapper";
-import { CommonResourceService } from "../../common/api/common-resource-service";
+import { CommonResourceService } from "~/common/api/common-resource-service";
 import * as _ from "lodash";
 
 export class CollectionFilesService {
index 860308179c87927ade63dc67aff8386321225b0a..c3fd43ead1d0d4c013e066f0331712710af9f38f 100644 (file)
@@ -3,8 +3,8 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { uniqBy, groupBy } from 'lodash';
-import { KeepManifestStream, KeepManifestStreamFile, KeepManifest } from "../../models/keep-manifest";
-import { TreeNode, setNode, createTree, getNodeDescendants, getNodeValue } from '../../models/tree';
+import { KeepManifestStream, KeepManifestStreamFile, KeepManifest } from "~/models/keep-manifest";
+import { TreeNode, setNode, createTree, getNodeDescendants, getNodeValue } from '~/models/tree';
 import { CollectionFilesTree, CollectionFile, CollectionDirectory, createCollectionDirectory, createCollectionFile, CollectionFileType } from '../../models/collection-file';
 
 export const mapCollectionFilesTreeToManifest = (tree: CollectionFilesTree): KeepManifest => {
index b0fc55a49ba0ec48017c26383272a82914a72b10..d564f33ec03a13634d11fb84052ef3e814ea6ba7 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { KeepManifestStream, KeepManifestStreamFile, KeepManifest } from "../../models/keep-manifest";
+import { KeepManifestStream, KeepManifestStreamFile, KeepManifest } from "~/models/keep-manifest";
 
 /**
  * Documentation [http://doc.arvados.org/api/storage.html](http://doc.arvados.org/api/storage.html)
index 4d750362e3972c339d95ca27f18aff7d3bed82e0..1c8eb5e9418f75fd9ac04561f44a41cc80eef90d 100644 (file)
@@ -2,15 +2,15 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { CommonResourceService } from "../../common/api/common-resource-service";
-import { CollectionResource } from "../../models/collection";
+import { CommonResourceService } from "~/common/api/common-resource-service";
+import { CollectionResource } from "~/models/collection";
 import axios, { AxiosInstance } from "axios";
 import { KeepService } from "../keep-service/keep-service";
-import { FilterBuilder } from "../../common/api/filter-builder";
-import { CollectionFile, createCollectionFile } from "../../models/collection-file";
+import { FilterBuilder } from "~/common/api/filter-builder";
+import { CollectionFile, createCollectionFile } from "~/models/collection-file";
 import { parseKeepManifestText, stringifyKeepManifest } from "../collection-files-service/collection-manifest-parser";
 import * as _ from "lodash";
-import { KeepManifestStream } from "../../models/keep-manifest";
+import { KeepManifestStream } from "~/models/keep-manifest";
 
 export type UploadProgress = (fileId: number, loaded: number, total: number, currentTime: number) => void;
 
@@ -84,10 +84,10 @@ export class CollectionService extends CommonResourceService<CollectionResource>
     }
 
     uploadFiles(collectionUuid: string, files: File[], onProgress?: UploadProgress): Promise<CollectionResource | never> {
-        const filters = FilterBuilder.create()
+        const filters = new FilterBuilder()
             .addEqual("service_type", "proxy");
 
-        return this.keepService.list({ filters }).then(data => {
+        return this.keepService.list({ filters: filters.getFilters() }).then(data => {
             if (data.items && data.items.length > 0) {
                 const serviceHost =
                     (data.items[0].serviceSslFlag ? "https://" : "http://") +
diff --git a/src/services/favorite-service/favorite-order-builder.ts b/src/services/favorite-service/favorite-order-builder.ts
deleted file mode 100644 (file)
index 0d9a75e..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { LinkResource } from "../../models/link";
-import { GroupContentsResource, GroupContentsResourcePrefix } from "../groups-service/groups-service";
-import { OrderBuilder } from "../../common/api/order-builder";
-
-export class FavoriteOrderBuilder {
-
-    static create(
-        linkOrder = OrderBuilder.create<LinkResource>(), 
-        contentOrder = OrderBuilder.create<GroupContentsResource>()) {
-        return new FavoriteOrderBuilder(linkOrder, contentOrder);
-    }
-
-    private constructor(
-        private linkOrder: OrderBuilder<LinkResource>,
-        private contentOrder: OrderBuilder<GroupContentsResource>
-    ) { }
-
-    addAsc(attribute: "name") {
-        const linkOrder = this.linkOrder.addAsc(attribute);
-        const contentOrder = this.contentOrder
-            .concat(OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.COLLECTION).addAsc(attribute))
-            .concat(OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROCESS).addAsc(attribute))
-            .concat(OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROJECT).addAsc(attribute));
-        return FavoriteOrderBuilder.create(linkOrder, contentOrder);
-    }
-
-    addDesc(attribute: "name") {
-        const linkOrder = this.linkOrder.addDesc(attribute);
-        const contentOrder = this.contentOrder
-            .concat(OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.COLLECTION).addDesc(attribute))
-            .concat(OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROCESS).addDesc(attribute))
-            .concat(OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROJECT).addDesc(attribute));
-        return FavoriteOrderBuilder.create(linkOrder, contentOrder);
-    }
-
-    getLinkOrder() {
-        return this.linkOrder;
-    }
-
-    getContentOrder() {
-        return this.contentOrder;
-    }
-
-}
\ No newline at end of file
index 3bc959df353e09b4a1bf3e0b3477390c4c74aac0..47211fed1ff78b30c5c00c37208c8a410d4fc21a 100644 (file)
@@ -3,11 +3,11 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { LinkService } from "../link-service/link-service";
-import { GroupsService, GroupContentsResource } from "../groups-service/groups-service";
+import { GroupsService } from "../groups-service/groups-service";
 import { FavoriteService } from "./favorite-service";
-import { LinkClass, LinkResource } from "../../models/link";
-import { mockResourceService } from "../../common/api/common-resource-service.test";
-import { FilterBuilder } from "../../common/api/filter-builder";
+import { LinkClass } from "~/models/link";
+import { mockResourceService } from "~/common/api/common-resource-service.test";
+import { FilterBuilder } from "~/common/api/filter-builder";
 
 describe("FavoriteService", () => {
 
@@ -38,8 +38,7 @@ describe("FavoriteService", () => {
 
     it("unmarks resource as favorite", async () => {
         const list = jest.fn().mockReturnValue(Promise.resolve({ items: [{ uuid: "linkUuid" }] }));
-        const filters = FilterBuilder
-            .create()
+        const filters = new FilterBuilder()
             .addEqual('tailUuid', "userUuid")
             .addEqual('headUuid', "resourceUuid")
             .addEqual('linkClass', LinkClass.STAR);
@@ -56,12 +55,11 @@ describe("FavoriteService", () => {
 
     it("lists favorite resources", async () => {
         const list = jest.fn().mockReturnValue(Promise.resolve({ items: [{ headUuid: "headUuid" }] }));
-        const listFilters = FilterBuilder
-            .create()
+        const listFilters = new FilterBuilder()
             .addEqual('tailUuid', "userUuid")
             .addEqual('linkClass', LinkClass.STAR);
         const contents = jest.fn().mockReturnValue(Promise.resolve({ items: [{ uuid: "resourceUuid" }] }));
-        const contentFilters = FilterBuilder.create().addIn('uuid', ["headUuid"]);
+        const contentFilters = new FilterBuilder().addIn('uuid', ["headUuid"]);
         linkService.list = list;
         groupService.contents = contents;
         const favoriteService = new FavoriteService(linkService, groupService);
@@ -76,8 +74,7 @@ describe("FavoriteService", () => {
 
     it("checks if resources are present in favorites", async () => {
         const list = jest.fn().mockReturnValue(Promise.resolve({ items: [{ headUuid: "foo" }] }));
-        const listFilters = FilterBuilder
-            .create()
+        const listFilters = new FilterBuilder()
             .addIn("headUuid", ["foo", "oof"])
             .addEqual("tailUuid", "userUuid")
             .addEqual("linkClass", LinkClass.STAR);
index 35dbbaf7ba1151ce5db0618c06142917095c73ad..7a49c8ccbac3555af028536815f38ade60c08de5 100644 (file)
@@ -4,24 +4,23 @@
 
 import { LinkService } from "../link-service/link-service";
 import { GroupsService, GroupContentsResource } from "../groups-service/groups-service";
-import { LinkResource, LinkClass } from "../../models/link";
-import { FilterBuilder } from "../../common/api/filter-builder";
-import { ListArguments, ListResults } from "../../common/api/common-resource-service";
-import { FavoriteOrderBuilder } from "./favorite-order-builder";
-import { OrderBuilder } from "../../common/api/order-builder";
+import { LinkClass } from "~/models/link";
+import { FilterBuilder, joinFilters } from "~/common/api/filter-builder";
+import { ListResults } from "~/common/api/common-resource-service";
 
 export interface FavoriteListArguments {
     limit?: number;
     offset?: number;
-    filters?: FilterBuilder;
-    order?: FavoriteOrderBuilder;
+    filters?: string;
+    linkOrder?: string;
+    contentOrder?: string;
 }
 
 export class FavoriteService {
     constructor(
         private linkService: LinkService,
         private groupsService: GroupsService
-    ) { }
+    ) {}
 
     create(data: { userUuid: string; resource: { uuid: string; name: string } }) {
         return this.linkService.create({
@@ -36,36 +35,36 @@ export class FavoriteService {
     delete(data: { userUuid: string; resourceUuid: string; }) {
         return this.linkService
             .list({
-                filters: FilterBuilder
-                    .create()
+                filters: new FilterBuilder()
                     .addEqual('tailUuid', data.userUuid)
                     .addEqual('headUuid', data.resourceUuid)
                     .addEqual('linkClass', LinkClass.STAR)
+                    .getFilters()
             })
             .then(results => Promise.all(
                 results.items.map(item => this.linkService.delete(item.uuid))));
     }
 
-    list(userUuid: string, { filters, limit, offset, order }: FavoriteListArguments = {}): Promise<ListResults<GroupContentsResource>> {
-        const listFilter = FilterBuilder
-            .create()
+    list(userUuid: string, { filters, limit, offset, linkOrder, contentOrder }: FavoriteListArguments = {}): Promise<ListResults<GroupContentsResource>> {
+        const listFilters = new FilterBuilder()
             .addEqual('tailUuid', userUuid)
-            .addEqual('linkClass', LinkClass.STAR);
+            .addEqual('linkClass', LinkClass.STAR)
+            .getFilters();
 
         return this.linkService
             .list({
-                filters: filters ? filters.concat(listFilter) : listFilter,
+                filters: joinFilters(filters, listFilters),
                 limit,
                 offset,
-                order: order ? order.getLinkOrder() : OrderBuilder.create<LinkResource>()
+                order: linkOrder
             })
             .then(results => {
                 const uuids = results.items.map(item => item.headUuid);
                 return this.groupsService.contents(userUuid, {
                     limit,
                     offset,
-                    order: order ? order.getContentOrder() : OrderBuilder.create<GroupContentsResource>(),
-                    filters: FilterBuilder.create().addIn('uuid', uuids),
+                    order: contentOrder,
+                    filters: new FilterBuilder().addIn('uuid', uuids).getFilters(),
                     recursive: true
                 });
             });
@@ -74,11 +73,11 @@ export class FavoriteService {
     checkPresenceInFavorites(userUuid: string, resourceUuids: string[]): Promise<Record<string, boolean>> {
         return this.linkService
             .list({
-                filters: FilterBuilder
-                    .create()
+                filters: new FilterBuilder()
                     .addIn("headUuid", resourceUuids)
                     .addEqual("tailUuid", userUuid)
                     .addEqual("linkClass", LinkClass.STAR)
+                    .getFilters()
             })
             .then(({ items }) => resourceUuids.reduce((results, uuid) => {
                 const isFavorite = items.some(item => item.headUuid === uuid);
index a0f27a4abbe27f26939e4d5be7c368f580eb9b32..822c810ef7ed0203666bdba829c0b13fced7d5ba 100644 (file)
@@ -3,20 +3,18 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as _ from "lodash";
-import { CommonResourceService, ListResults } from "../../common/api/common-resource-service";
-import { FilterBuilder } from "../../common/api/filter-builder";
-import { OrderBuilder } from "../../common/api/order-builder";
+import { CommonResourceService, ListResults } from "~/common/api/common-resource-service";
 import { AxiosInstance } from "axios";
-import { GroupResource } from "../../models/group";
-import { CollectionResource } from "../../models/collection";
-import { ProjectResource } from "../../models/project";
-import { ProcessResource } from "../../models/process";
+import { GroupResource } from "~/models/group";
+import { CollectionResource } from "~/models/collection";
+import { ProjectResource } from "~/models/project";
+import { ProcessResource } from "~/models/process";
 
 export interface ContentsArguments {
     limit?: number;
     offset?: number;
-    order?: OrderBuilder;
-    filters?: FilterBuilder;
+    order?: string;
+    filters?: string;
     recursive?: boolean;
 }
 
@@ -35,8 +33,8 @@ export class GroupsService<T extends GroupResource = GroupResource> extends Comm
         const { filters, order, ...other } = args;
         const params = {
             ...other,
-            filters: filters ? filters.serialize() : undefined,
-            order: order ? order.getOrder() : undefined
+            filters: filters ? `[${filters}]` : undefined,
+            order: order ? order : undefined
         };
         return this.serverApi
             .get(this.resourceType + `${uuid}/contents/`, {
index 188e45af2ff6635098b01a3d704cb8ae20899c06..fd49823a4730a29b8a1c45e3271de991650d9d09 100644 (file)
@@ -2,9 +2,9 @@
 //\r
 // SPDX-License-Identifier: AGPL-3.0\r
 \r
-import { CommonResourceService } from "../../common/api/common-resource-service";\r
+import { CommonResourceService } from "~/common/api/common-resource-service";\r
 import { AxiosInstance } from "axios";\r
-import { KeepResource } from "../../models/keep";\r
+import { KeepResource } from "~/models/keep";\r
 \r
 export class KeepService extends CommonResourceService<KeepResource> {\r
     constructor(serverApi: AxiosInstance) {\r
index 4c12cd0ba9911f916b772ffcc3e9da925b6f9302..8724904ef2a5eb0d61ed9ba101f82d5eda3bff60 100644 (file)
@@ -2,12 +2,12 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { CommonResourceService } from "../../common/api/common-resource-service";
-import { LinkResource } from "../../models/link";
+import { CommonResourceService } from "~/common/api/common-resource-service";
+import { LinkResource } from "~/models/link";
 import { AxiosInstance } from "axios";
 
 export class LinkService extends CommonResourceService<LinkResource> {
     constructor(serverApi: AxiosInstance) {
         super(serverApi, "links");
     }
-}
\ No newline at end of file
+}
index eb7ea7438dd54776778515b6d51e2c463f1e8339..93063462dcf33b62d0664c0dbd1f8fcf4e4881b6 100644 (file)
@@ -4,8 +4,7 @@
 
 import axios from "axios";
 import { ProjectService } from "./project-service";
-import { FilterBuilder } from "../../common/api/filter-builder";
-import { ProjectResource } from "../../models/project";
+import { FilterBuilder } from "~/common/api/filter-builder";
 
 describe("CommonResourceService", () => {
     const axiosInstance = axios.create();
@@ -20,19 +19,16 @@ describe("CommonResourceService", () => {
         });
     });
 
-
     it("#list has groupClass filter set by default", async () => {
         axiosInstance.get = jest.fn(() => Promise.resolve({ data: {} }));
         const projectService = new ProjectService(axiosInstance);
         const resource = await projectService.list();
         expect(axiosInstance.get).toHaveBeenCalledWith("/groups/", {
             params: {
-                filters: FilterBuilder
-                    .create()
+                filters: new FilterBuilder()
                     .addEqual("groupClass", "project")
-                    .serialize()
+                    .getFilters()
             }
         });
     });
-
 });
index 13c60ad3140168b499243f50a36c73c3681e7367..e916f3c0a4da9d8be388a8577d3d304115385007 100644 (file)
@@ -2,11 +2,11 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { GroupsService, ContentsArguments } from "../groups-service/groups-service";
-import { ProjectResource } from "../../models/project";
-import { GroupClass } from "../../models/group";
-import { ListArguments } from "../../common/api/common-resource-service";
-import { FilterBuilder } from "../../common/api/filter-builder";
+import { GroupsService } from "../groups-service/groups-service";
+import { ProjectResource } from "~/models/project";
+import { GroupClass } from "~/models/group";
+import { ListArguments } from "~/common/api/common-resource-service";
+import { FilterBuilder, joinFilters } from "~/common/api/filter-builder";
 
 export class ProjectService extends GroupsService<ProjectResource> {
 
@@ -18,18 +18,12 @@ export class ProjectService extends GroupsService<ProjectResource> {
     list(args: ListArguments = {}) {
         return super.list({
             ...args,
-            filters: this.addProjectFilter(args.filters)
+            filters: joinFilters(
+                args.filters,
+                new FilterBuilder()
+                    .addEqual("groupClass", GroupClass.PROJECT)
+                    .getFilters()
+            )
         });
     }
-
-    private addProjectFilter(filters?: FilterBuilder) {
-        return FilterBuilder
-            .create()
-            .concat(filters
-                ? filters
-                : FilterBuilder.create())
-            .concat(FilterBuilder
-                .create()
-                .addEqual("groupClass", GroupClass.PROJECT));
-    }
 }
index e77b5d3aa89b18a5fa8d4e1f3b2de2826f93f523..99f802dfe079521074c2fa143297e0d1bca0cb2d 100644 (file)
@@ -2,37 +2,29 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
+import Axios, { AxiosInstance } from "axios";
 import { AuthService } from "./auth-service/auth-service";
 import { GroupsService } from "./groups-service/groups-service";
 import { ProjectService } from "./project-service/project-service";
 import { LinkService } from "./link-service/link-service";
 import { FavoriteService } from "./favorite-service/favorite-service";
-import { AxiosInstance } from "axios";
 import { CollectionService } from "./collection-service/collection-service";
 import { TagService } from "./tag-service/tag-service";
-import Axios from "axios";
 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";
 
-export interface ServiceRepository {
-    apiClient: AxiosInstance;
+export type ServiceRepository = ReturnType<typeof createServices>;
 
-    authService: AuthService;
-    keepService: KeepService;
-    groupsService: GroupsService;
-    projectService: ProjectService;
-    linkService: LinkService;
-    favoriteService: FavoriteService;
-    tagService: TagService;
-    collectionService: CollectionService;
-    collectionFilesService: CollectionFilesService;
-}
-
-export const createServices = (baseUrl: string): ServiceRepository => {
+export const createServices = (config: Config) => {
     const apiClient = Axios.create();
-    apiClient.defaults.baseURL = `${baseUrl}/arvados/v1`;
+    apiClient.defaults.baseURL = `${config.apiHost}/arvados/v1`;
+
+    const webdavClient = new WebDAV();
+    webdavClient.defaults.baseURL = config.keepWebHost;
 
-    const authService = new AuthService(apiClient, baseUrl);
+    const authService = new AuthService(apiClient, config.apiHost);
     const keepService = new KeepService(apiClient);
     const groupsService = new GroupsService(apiClient);
     const projectService = new ProjectService(apiClient);
@@ -44,6 +36,7 @@ export const createServices = (baseUrl: string): ServiceRepository => {
 
     return {
         apiClient,
+        webdavClient,
         authService,
         keepService,
         groupsService,
@@ -55,3 +48,4 @@ export const createServices = (baseUrl: string): ServiceRepository => {
         collectionFilesService
     };
 };
+
index 084603eb0833a9f64211598acc899e2d96a95288..52e481a7975b38890c88c9d9309f68bf960e59fa 100644 (file)
@@ -3,10 +3,10 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { LinkService } from "../link-service/link-service";
-import { LinkClass } from "../../models/link";
-import { FilterBuilder } from "../../common/api/filter-builder";
-import { TagTailType, TagResource } from "../../models/tag";
-import { OrderBuilder } from "../../common/api/order-builder";
+import { LinkClass } from "~/models/link";
+import { FilterBuilder } from "~/common/api/filter-builder";
+import { TagTailType, TagResource } from "~/models/tag";
+import { OrderBuilder } from "~/common/api/order-builder";
 
 export class TagService {
 
@@ -25,15 +25,15 @@ export class TagService {
     }
 
     list(uuid: string) {
-        const filters = FilterBuilder
-            .create()
+        const filters = new FilterBuilder()
             .addEqual("headUuid", uuid)
             .addEqual("tailUuid", TagTailType.COLLECTION)
-            .addEqual("linkClass", LinkClass.TAG);
+            .addEqual("linkClass", LinkClass.TAG)
+            .getFilters();
 
-        const order = OrderBuilder
-            .create<TagResource>()
-            .addAsc('createdAt');
+        const order = new OrderBuilder<TagResource>()
+            .addAsc('createdAt')
+            .getOrder();
 
         return this.linkService
             .list({ filters, order })
@@ -41,5 +41,4 @@ export class TagService {
                 return results.items.map((tag => tag as TagResource ));
             });
     }
-
 }
index 6b81c31796a41dce91cce146f560978cd5510d56..33a5df90450e2cde772c73c8d80c7b06854c4f12 100644 (file)
@@ -4,9 +4,9 @@
 
 import { ofType, default as unionize, UnionOf } from "unionize";
 import { Dispatch } from "redux";
-import { User } from "../../models/user";
+import { User } from "~/models/user";
 import { RootState } from "../store";
-import { ServiceRepository } from "../../services/services";
+import { ServiceRepository } from "~/services/services";
 import { AxiosInstance } from "axios";
 
 export const authActions = unionize({
@@ -17,12 +17,15 @@ export const authActions = unionize({
     USER_DETAILS_REQUEST: {},
     USER_DETAILS_SUCCESS: ofType<User>()
 }, {
-    tag: 'type',
-    value: 'payload'
-});
+        tag: 'type',
+        value: 'payload'
+    });
 
-function setAuthorizationHeader(client: AxiosInstance, token: string) {
-    client.defaults.headers.common = {
+function setAuthorizationHeader(services: ServiceRepository, token: string) {
+    services.apiClient.defaults.headers.common = {
+        Authorization: `OAuth2 ${token}`
+    };
+    services.webdavClient.defaults.headers = {
         Authorization: `OAuth2 ${token}`
     };
 }
@@ -35,7 +38,7 @@ export const initAuth = () => (dispatch: Dispatch, getState: () => RootState, se
     const user = services.authService.getUser();
     const token = services.authService.getApiToken();
     if (token) {
-        setAuthorizationHeader(services.apiClient, token);
+        setAuthorizationHeader(services, token);
     }
     if (token && user) {
         dispatch(authActions.INIT({ user, token }));
@@ -44,7 +47,7 @@ export const initAuth = () => (dispatch: Dispatch, getState: () => RootState, se
 
 export const saveApiToken = (token: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
     services.authService.saveApiToken(token);
-    setAuthorizationHeader(services.apiClient, token);
+    setAuthorizationHeader(services, token);
     dispatch(authActions.SAVE_API_TOKEN(token));
 };
 
index 1ded88ead6361fc28e5252ca92a5430a46219ee5..217a27cc49cfccd5d465a25480670ac327029e3e 100644 (file)
@@ -11,10 +11,10 @@ import {
     USER_LAST_NAME_KEY,
     USER_OWNER_UUID_KEY,
     USER_UUID_KEY
-} from "../../services/auth-service/auth-service";
+} from "~/services/auth-service/auth-service";
 
 import 'jest-localstorage-mock';
-import { createServices } from "../../services/services";
+import { createServices } from "~/services/services";
 import { configureStore, RootStore } from "../store";
 import createBrowserHistory from "history/createBrowserHistory";
 
@@ -23,9 +23,9 @@ describe('auth-actions', () => {
     let store: RootStore;
 
     beforeEach(() => {
-        store = configureStore(createBrowserHistory(), createServices("/arvados/v1"));
+        store = configureStore(createBrowserHistory(), createServices({ apiHost: "/arvados/v1", keepWebHost: "" }));
         localStorage.clear();
-        reducer = authReducer(createServices("/arvados/v1"));
+        reducer = authReducer(createServices({ apiHost: "/arvados/v1", keepWebHost: "" }));
     });
 
     it('should initialise state with user and api token from local storage', () => {
index 0e05263d4301a7b3d94f0408670da9a9cc019fd5..8eeb7c3c636152c9087b868cd01ca71b1678c9df 100644 (file)
@@ -6,14 +6,14 @@ import { authReducer, AuthState } from "./auth-reducer";
 import { AuthAction, authActions } from "./auth-action";
 
 import 'jest-localstorage-mock';
-import { createServices } from "../../services/services";
+import { createServices } from "~/services/services";
 
 describe('auth-reducer', () => {
     let reducer: (state: AuthState | undefined, action: AuthAction) => any;
 
     beforeAll(() => {
         localStorage.clear();
-        reducer = authReducer(createServices("/arvados/v1"));
+        reducer = authReducer(createServices({ apiHost: "/arvados/v1", keepWebHost: "" }));
     });
 
     it('should correctly initialise state', () => {
@@ -25,7 +25,7 @@ describe('auth-reducer', () => {
             uuid: "uuid",
             ownerUuid: "ownerUuid"
         };
-        const state = reducer(initialState, authActions.INIT({user, token: "token"}));
+        const state = reducer(initialState, authActions.INIT({ user, token: "token" }));
         expect(state).toEqual({
             apiToken: "token",
             user
index 1546212b08fe846834bb84d61cf098dedf2c40c3..a4195322c867316ce201f8d03ea4c28bffd25825 100644 (file)
@@ -3,8 +3,8 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { authActions, AuthAction } from "./auth-action";
-import { User } from "../../models/user";
-import { ServiceRepository } from "../../services/services";
+import { User } from "~/models/user";
+import { ServiceRepository } from "~/services/services";
 
 export interface AuthState {
     user?: User;
index f2774f6fb384f2d72256e57a93bd9cda92d71493..0772210c8497fae4655a7cc7a004fb8e4d3067b2 100644 (file)
@@ -4,13 +4,13 @@
 
 import { unionize, ofType, UnionOf } from "unionize";
 import { Dispatch } from "redux";
-import { ResourceKind } from "../../models/resource";
-import { CollectionResource } from "../../models/collection";
+import { ResourceKind } from "~/models/resource";
+import { CollectionResource } from "~/models/collection";
 import { collectionPanelFilesAction } from "./collection-panel-files/collection-panel-files-actions";
-import { createTree } from "../../models/tree";
+import { createTree } from "~/models/tree";
 import { RootState } from "../store";
-import { ServiceRepository } from "../../services/services";
-import { TagResource, TagProperty } from "../../models/tag";
+import { ServiceRepository } from "~/services/services";
+import { TagResource, TagProperty } from "~/models/tag";
 import { snackbarActions } from "../snackbar/snackbar-actions";
 
 export const collectionPanelActions = unionize({
@@ -43,7 +43,7 @@ export const loadCollection = (uuid: string, kind: ResourceKind) =>
             });
     };
 
-export const loadCollectionTags = (uuid: string) => 
+export const loadCollectionTags = (uuid: string) =>
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         dispatch(collectionPanelActions.LOAD_COLLECTION_TAGS({ uuid }));
         return services.tagService
@@ -54,7 +54,7 @@ export const loadCollectionTags = (uuid: string) =>
     };
 
 
-export const createCollectionTag = (data: TagProperty) => 
+export const createCollectionTag = (data: TagProperty) =>
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         dispatch(collectionPanelActions.CREATE_COLLECTION_TAG({ data }));
         const item = getState().collectionPanel.item;
@@ -70,7 +70,7 @@ export const createCollectionTag = (data: TagProperty) =>
             });
     };
 
-export const deleteCollectionTag = (uuid: string) => 
+export const deleteCollectionTag = (uuid: string) =>
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         dispatch(collectionPanelActions.DELETE_COLLECTION_TAG({ uuid }));
         return services.linkService
@@ -82,4 +82,4 @@ export const deleteCollectionTag = (uuid: string) =>
                     hideDuration: 2000
                 }));
             });
-    };
\ No newline at end of file
+    };
index 463d49c5ef9de106349e7bd22135fe2fb5aa8882..09821083ee4c5bda6fce5f6bb6faf1524dad6eb4 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { default as unionize, ofType, UnionOf } from "unionize";
-import { CollectionFilesTree } from "../../../models/collection-file";
+import { CollectionFilesTree } from "~/models/collection-file";
 
 export const collectionPanelFilesAction = unionize({
     SET_COLLECTION_FILES: ofType<CollectionFilesTree>(),
@@ -13,4 +13,4 @@ export const collectionPanelFilesAction = unionize({
     UNSELECT_ALL_COLLECTION_FILES: ofType<{}>(),
 }, { tag: 'type', value: 'payload' });
 
-export type CollectionPanelFilesAction = UnionOf<typeof collectionPanelFilesAction>;
\ No newline at end of file
+export type CollectionPanelFilesAction = UnionOf<typeof collectionPanelFilesAction>;
index 94b71ffb249d729751b9b8e9486d9ce19ca75b8f..90dedaaaa4798dedf2d84e66a29c1fe6b75033ad 100644 (file)
@@ -4,8 +4,8 @@
 
 import { collectionPanelFilesReducer } from "./collection-panel-files-reducer";
 import { collectionPanelFilesAction } from "./collection-panel-files-actions";
-import { CollectionFile, CollectionDirectory, createCollectionFile, createCollectionDirectory } from "../../../models/collection-file";
-import { createTree, setNode, getNodeValue, mapTreeValues, Tree } from "../../../models/tree";
+import { CollectionFile, CollectionDirectory, createCollectionFile, createCollectionDirectory } from "~/models/collection-file";
+import { createTree, setNode, getNodeValue, mapTreeValues } from "~/models/tree";
 import { CollectionPanelFile, CollectionPanelDirectory } from "./collection-panel-files-state";
 
 describe('CollectionPanelFilesReducer', () => {
index ca518f0ea52e018f697eefe78de5b65937adb7e9..2a3aac744737691bbf6c2a472f002eca0ff363db 100644 (file)
@@ -4,8 +4,8 @@
 
 import { CollectionPanelFilesState, CollectionPanelFile, CollectionPanelDirectory, mapCollectionFileToCollectionPanelFile } from "./collection-panel-files-state";
 import { CollectionPanelFilesAction, collectionPanelFilesAction } from "./collection-panel-files-actions";
-import { createTree, mapTreeValues, getNode, setNode, getNodeAncestors, getNodeDescendants, setNodeValueWith, mapTree } from "../../../models/tree";
-import { CollectionFileType } from "../../../models/collection-file";
+import { createTree, mapTreeValues, getNode, setNode, getNodeAncestors, getNodeDescendants, setNodeValueWith, mapTree } from "~/models/tree";
+import { CollectionFileType } from "~/models/collection-file";
 
 export const collectionPanelFilesReducer = (state: CollectionPanelFilesState = createTree(), action: CollectionPanelFilesAction) => {
     return collectionPanelFilesAction.match(action, {
index d6f2fa4a98db7d340081c450a7a4c9e05be06bb0..f7955eb6d0fe8a544003095dce3de8bd4988fc63 100644 (file)
@@ -2,8 +2,8 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { CollectionFile, CollectionDirectory, CollectionFileType } from '../../../models/collection-file';
-import { Tree, TreeNode } from '../../../models/tree';
+import { CollectionFile, CollectionDirectory, CollectionFileType } from '~/models/collection-file';
+import { Tree, TreeNode } from '~/models/tree';
 
 export type CollectionPanelFilesState = Tree<CollectionPanelDirectory | CollectionPanelFile>;
 
@@ -23,4 +23,4 @@ export const mapCollectionFileToCollectionPanelFile = (node: TreeNode<Collection
             ? { ...node.value, selected: false, collapsed: true }
             : { ...node.value, selected: false }
     };
-};
\ No newline at end of file
+};
index 44b778980bafa68eade5afb259880b5a7e7bf1e2..2c3edf1a5afce13ec9059f523f042df3eef9751a 100644 (file)
@@ -3,8 +3,8 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { collectionPanelActions, CollectionPanelAction } from "./collection-panel-action";
-import { CollectionResource } from "../../models/collection";
-import { TagResource } from "../../models/tag";
+import { CollectionResource } from "~/models/collection";
+import { TagResource } from "~/models/tag";
 
 export interface CollectionPanelState {
     item: CollectionResource | null;
index d0a66b4cc0f629cf60dc935beee2cf3c42aadfbd..323ba8d8e86a844a9dca026772a9058593687aba 100644 (file)
@@ -6,8 +6,8 @@ import { default as unionize, ofType, UnionOf } from "unionize";
 import { Dispatch } from "redux";
 
 import { RootState } from "../../store";
-import { CollectionResource } from '../../../models/collection';
-import { ServiceRepository } from "../../../services/services";
+import { CollectionResource } from '~/models/collection';
+import { ServiceRepository } from "~/services/services";
 import { collectionUploaderActions } from "../uploader/collection-uploader-actions";
 import { reset } from "redux-form";
 
index bb9f4d3072d47bdd7fda30ac5784aeaa4e259b40..2f520d4a384f84b63982bfa12fd13dd8ece81e9b 100644 (file)
@@ -6,11 +6,12 @@ import { default as unionize, ofType, UnionOf } from "unionize";
 import { Dispatch } from "redux";
 
 import { RootState } from "../../store";
-import { ServiceRepository } from "../../../services/services";
-import { CollectionResource } from '../../../models/collection';
+import { ServiceRepository } from "~/services/services";
+import { CollectionResource } from '~/models/collection';
 import { initialize } from 'redux-form';
 import { collectionPanelActions } from "../../collection-panel/collection-panel-action";
 import { ContextMenuResource } from "../../context-menu/context-menu-reducer";
+import { updateDetails } from "~/store/details-panel/details-panel-action";
 
 export const collectionUpdaterActions = unionize({
     OPEN_COLLECTION_UPDATER: ofType<{ uuid: string }>(),
@@ -40,6 +41,7 @@ export const updateCollection = (collection: Partial<CollectionResource>) =>
             .then(collection => {
                     dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: collection as CollectionResource }));
                     dispatch(collectionUpdaterActions.UPDATE_COLLECTION_SUCCESS());
+                    dispatch<any>(updateDetails(collection));
                 }
             );
     };
index 6dd7af92856a909b968fa328e0b9011df457a073..abb293fdcf2dd062cfdd0ffb20b6f6ed1ad5a0c0 100644 (file)
@@ -3,8 +3,8 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { default as unionize, ofType, UnionOf } from "unionize";
-import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
-import { DataColumns } from "../../components/data-table/data-table";
+import { DataTableFilterItem } from "~/components/data-table-filters/data-table-filters";
+import { DataColumns } from "~/components/data-table/data-table";
 
 export const dataExplorerActions = unionize({
     RESET_PAGINATION: ofType<{ id: string }>(),
index 14be4ea7b8c074551989d0dde93ae7350ff5154d..7c64020ef3b46b7e9f2ff85b4e078741a352bab4 100644 (file)
@@ -4,6 +4,8 @@
 
 import { Dispatch, MiddlewareAPI } from "redux";
 import { RootState } from "../store";
+import { DataColumns } from "~/components/data-table/data-table";
+import { DataTableFilterItem } from "~/components/data-table-filters/data-table-filters";
 
 export abstract class DataExplorerMiddlewareService {
     protected readonly id: string;
@@ -16,5 +18,10 @@ export abstract class DataExplorerMiddlewareService {
         return this.id;
     }
 
+    public getColumnFilters<T, F extends DataTableFilterItem>(columns: DataColumns<T, F>, columnName: string): F[] {
+        const column = columns.find(c => c.name === columnName);
+        return column ? column.filters.filter(f => f.selected) : [];
+    }
+
     abstract requestItems(api: MiddlewareAPI<Dispatch, RootState>): void;
 }
index 6b8297b0c8c3bb84afc55a4fe16e5e2772db9a06..2a88817cecc8ad9256b6ee9f33b3c212df83a78b 100644 (file)
@@ -5,12 +5,13 @@
 import { DataExplorerMiddlewareService } from "./data-explorer-middleware-service";
 import { dataExplorerMiddleware } from "./data-explorer-middleware";
 import { MiddlewareAPI } from "redux";
-import { DataColumns } from "../../components/data-table/data-table";
+import { DataColumns } from "~/components/data-table/data-table";
 import { dataExplorerActions } from "./data-explorer-action";
+import { SortDirection } from "~/components/data-table/data-column";
 
 
 describe("DataExplorerMiddleware", () => {
-    
+
     it("handles only actions that are identified by service id", () => {
         const config = {
             id: "ServiceId",
@@ -18,6 +19,8 @@ describe("DataExplorerMiddleware", () => {
                 name: "Column",
                 selected: true,
                 configurable: false,
+                sortDirection: SortDirection.NONE,
+                filters: [],
                 render: jest.fn()
             }],
             requestItems: jest.fn(),
@@ -44,6 +47,8 @@ describe("DataExplorerMiddleware", () => {
                 name: "Column",
                 selected: true,
                 configurable: false,
+                sortDirection: SortDirection.NONE,
+                filters: [],
                 render: jest.fn()
             }],
             requestItems: jest.fn(),
index c54a86af4aa6a44559d5b7f41624ac483ea6cd2b..6b1c90798962032ddc273005b1f1b0c7d41de123 100644 (file)
@@ -13,7 +13,8 @@ describe('data-explorer-reducer', () => {
         const columns: DataColumns<any> = [{
             name: "Column 1",
             render: jest.fn(),
-            selected: true
+            selected: true,
+            configurable: true
         }];
         const state = dataExplorerReducer(undefined,
             dataExplorerActions.SET_COLUMNS({ id: "Data explorer", columns }));
@@ -25,11 +26,13 @@ describe('data-explorer-reducer', () => {
             name: "Column 1",
             render: jest.fn(),
             selected: true,
+            configurable: true,
             sortDirection: SortDirection.ASC
         }, {
             name: "Column 2",
             render: jest.fn(),
             selected: true,
+            configurable: true,
             sortDirection: SortDirection.NONE,
         }];
         const state = dataExplorerReducer({ "Data explorer": { ...initialDataExplorer, columns } },
@@ -43,6 +46,7 @@ describe('data-explorer-reducer', () => {
             name: "Column 1",
             render: jest.fn(),
             selected: true,
+            configurable: true
         }];
 
         const filters: DataTableFilterItem[] = [{
@@ -55,7 +59,7 @@ describe('data-explorer-reducer', () => {
     });
 
     it('should set items', () => {
-        const state = dataExplorerReducer({ "Data explorer": undefined },
+        const state = dataExplorerReducer({},
             dataExplorerActions.SET_ITEMS({
                 id: "Data explorer",
                 items: ["Item 1", "Item 2"],
@@ -67,13 +71,13 @@ describe('data-explorer-reducer', () => {
     });
 
     it('should set page', () => {
-        const state = dataExplorerReducer({ "Data explorer": undefined },
+        const state = dataExplorerReducer({},
             dataExplorerActions.SET_PAGE({ id: "Data explorer", page: 2 }));
         expect(state["Data explorer"].page).toEqual(2);
     });
 
     it('should set rows per page', () => {
-        const state = dataExplorerReducer({ "Data explorer": undefined },
+        const state = dataExplorerReducer({},
             dataExplorerActions.SET_ROWS_PER_PAGE({ id: "Data explorer", rowsPerPage: 5 }));
         expect(state["Data explorer"].rowsPerPage).toEqual(5);
     });
index 1fde652d07b3941bfa9b02207352101a72a4ba4a..175cd0b2817fd0ea419f8474f1326bb16bd2dc20 100644 (file)
@@ -2,10 +2,10 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { DataColumn, toggleSortDirection, resetSortDirection } from "../../components/data-table/data-column";
+import { DataColumn, toggleSortDirection, resetSortDirection } from "~/components/data-table/data-column";
 import { dataExplorerActions, DataExplorerAction } from "./data-explorer-action";
-import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
-import { DataColumns } from "../../components/data-table/data-table";
+import { DataTableFilterItem } from "~/components/data-table-filters/data-table-filters";
+import { DataColumns } from "~/components/data-table/data-table";
 
 export interface DataExplorer {
     columns: DataColumns<any>;
index c4acf5aa9b3710fac0a3f61a905132651f8709da..b8021fb6a0d81d12588b2efe921a0d3142c7df6c 100644 (file)
@@ -4,14 +4,15 @@
 
 import { unionize, ofType, UnionOf } from "unionize";
 import { Dispatch } from "redux";
-import { Resource, ResourceKind } from "../../models/resource";
+import { Resource, ResourceKind } from "~/models/resource";
 import { RootState } from "../store";
-import { ServiceRepository } from "../../services/services";
+import { ServiceRepository } from "~/services/services";
 
 export const detailsPanelActions = unionize({
     TOGGLE_DETAILS_PANEL: ofType<{}>(),
     LOAD_DETAILS: ofType<{ uuid: string, kind: ResourceKind }>(),
     LOAD_DETAILS_SUCCESS: ofType<{ item: Resource }>(),
+    UPDATE_DETAILS: ofType<{ item: Resource }>()
 }, { tag: 'type', value: 'payload' });
 
 export type DetailsPanelAction = UnionOf<typeof detailsPanelActions>;
@@ -23,6 +24,16 @@ export const loadDetails = (uuid: string, kind: ResourceKind) =>
         dispatch(detailsPanelActions.LOAD_DETAILS_SUCCESS({ item }));
     };
 
+export const updateDetails = (item: Resource) => 
+    async (dispatch: Dispatch, getState: () => RootState) => {
+        const currentItem = getState().detailsPanel.item;
+        if (currentItem && (currentItem.uuid === item.uuid)) {
+            dispatch(detailsPanelActions.UPDATE_DETAILS({ item }));
+            dispatch(detailsPanelActions.LOAD_DETAILS_SUCCESS({ item }));
+        }
+    };
+
+
 const getService = (services: ServiceRepository, kind: ResourceKind) => {
     switch (kind) {
         case ResourceKind.PROJECT:
index 97de4a9a50b041b9583a2afa4231e446e1d3008b..f22add3d49b08810a2c5cf248ca4f90503dc0a1d 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { detailsPanelActions, DetailsPanelAction } from "./details-panel-action";
-import { Resource } from "../../models/resource";
+import { Resource } from "~/models/resource";
 
 export interface DetailsPanelState {
     item: Resource | null;
@@ -18,7 +18,6 @@ const initialState = {
 export const detailsPanelReducer = (state: DetailsPanelState = initialState, action: DetailsPanelAction) =>
     detailsPanelActions.match(action, {
         default: () => state,
-        LOAD_DETAILS: () => state,
         LOAD_DETAILS_SUCCESS: ({ item }) => ({ ...state, item }),
         TOGGLE_DETAILS_PANEL: () => ({ ...state, isOpened: !state.isOpened })
     });
index be4b645cdea2424fdb522a6efa9fef6afc6d495e..1c2f062252b2101224d62b690d955849c327b698 100644 (file)
@@ -3,18 +3,19 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { DataExplorerMiddlewareService } from "../data-explorer/data-explorer-middleware-service";
-import { FavoritePanelFilter, FavoritePanelColumnNames } from "../../views/favorite-panel/favorite-panel";
+import { FavoritePanelColumnNames, FavoritePanelFilter } from "~/views/favorite-panel/favorite-panel";
 import { RootState } from "../store";
-import { DataColumns } from "../../components/data-table/data-table";
-import { FavoritePanelItem, resourceToDataItem } from "../../views/favorite-panel/favorite-panel-item";
-import { FavoriteOrderBuilder } from "../../services/favorite-service/favorite-order-builder";
-import { ServiceRepository } from "../../services/services";
-import { SortDirection } from "../../components/data-table/data-column";
-import { FilterBuilder } from "../../common/api/filter-builder";
-import { LinkResource } from "../../models/link";
+import { DataColumns } from "~/components/data-table/data-table";
+import { FavoritePanelItem, resourceToDataItem } from "~/views/favorite-panel/favorite-panel-item";
+import { ServiceRepository } from "~/services/services";
+import { SortDirection } from "~/components/data-table/data-column";
+import { FilterBuilder } from "~/common/api/filter-builder";
 import { checkPresenceInFavorites } from "../favorites/favorites-actions";
 import { favoritePanelActions } from "./favorite-panel-action";
 import { Dispatch, MiddlewareAPI } from "redux";
+import { OrderBuilder, OrderDirection } from "~/common/api/order-builder";
+import { LinkResource } from "~/models/link";
+import { GroupContentsResource, GroupContentsResourcePrefix } from "~/services/groups-service/groups-service";
 
 export class FavoritePanelMiddlewareService extends DataExplorerMiddlewareService {
     constructor(private services: ServiceRepository, id: string) {
@@ -24,47 +25,51 @@ export class FavoritePanelMiddlewareService extends DataExplorerMiddlewareServic
     requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
         const dataExplorer = api.getState().dataExplorer[this.getId()];
         const columns = dataExplorer.columns as DataColumns<FavoritePanelItem, FavoritePanelFilter>;
-        const sortColumn = dataExplorer.columns.find(
-            ({ sortDirection }) => sortDirection !== undefined && sortDirection !== "none"
-        );
-        const typeFilters = getColumnFilters(columns, FavoritePanelColumnNames.TYPE);
-        const order = FavoriteOrderBuilder.create();
-        if (typeFilters.length > 0) {
-            this.services.favoriteService
-                .list(this.services.authService.getUuid()!, {
-                    limit: dataExplorer.rowsPerPage,
-                    offset: dataExplorer.page * dataExplorer.rowsPerPage,
-                    order: sortColumn!.name === FavoritePanelColumnNames.NAME
-                        ? sortColumn!.sortDirection === SortDirection.ASC
-                            ? order.addDesc("name")
-                            : order.addAsc("name")
-                        : order,
-                    filters: FilterBuilder
-                        .create()
-                        .addIsA("headUuid", typeFilters.map(filter => filter.type))
-                        .addILike("name", dataExplorer.searchValue)
-                })
-                .then(response => {
-                    api.dispatch(favoritePanelActions.SET_ITEMS({
-                        items: response.items.map(resourceToDataItem),
-                        itemsAvailable: response.itemsAvailable,
-                        page: Math.floor(response.offset / response.limit),
-                        rowsPerPage: response.limit
-                    }));
-                    api.dispatch<any>(checkPresenceInFavorites(response.items.map(item => item.uuid)));
-                });
-        } else {
-            api.dispatch(favoritePanelActions.SET_ITEMS({
-                items: [],
-                itemsAvailable: 0,
-                page: 0,
-                rowsPerPage: dataExplorer.rowsPerPage
-            }));
+        const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE);
+        const typeFilters = this.getColumnFilters(columns, FavoritePanelColumnNames.TYPE);
+
+        const linkOrder = new OrderBuilder<LinkResource>();
+        const contentOrder = new OrderBuilder<GroupContentsResource>();
+
+        if (sortColumn && sortColumn.name === FavoritePanelColumnNames.NAME) {
+            const direction = sortColumn.sortDirection === SortDirection.ASC
+                ? OrderDirection.ASC
+                : OrderDirection.DESC;
+
+            linkOrder.addOrder(direction, "name");
+            contentOrder
+                .addOrder(direction, "name", GroupContentsResourcePrefix.COLLECTION)
+                .addOrder(direction, "name", GroupContentsResourcePrefix.PROCESS)
+                .addOrder(direction, "name", GroupContentsResourcePrefix.PROJECT);
         }
+
+        this.services.favoriteService
+            .list(this.services.authService.getUuid()!, {
+                limit: dataExplorer.rowsPerPage,
+                offset: dataExplorer.page * dataExplorer.rowsPerPage,
+                linkOrder: linkOrder.getOrder(),
+                contentOrder: contentOrder.getOrder(),
+                filters: new FilterBuilder()
+                    .addIsA("headUuid", typeFilters.map(filter => filter.type))
+                    .addILike("name", dataExplorer.searchValue)
+                    .getFilters()
+            })
+            .then(response => {
+                api.dispatch(favoritePanelActions.SET_ITEMS({
+                    items: response.items.map(resourceToDataItem),
+                    itemsAvailable: response.itemsAvailable,
+                    page: Math.floor(response.offset / response.limit),
+                    rowsPerPage: response.limit
+                }));
+                api.dispatch<any>(checkPresenceInFavorites(response.items.map(item => item.uuid)));
+            })
+            .catch(() => {
+                api.dispatch(favoritePanelActions.SET_ITEMS({
+                    items: [],
+                    itemsAvailable: 0,
+                    page: 0,
+                    rowsPerPage: dataExplorer.rowsPerPage
+                }));
+            });
     }
 }
-
-const getColumnFilters = (columns: DataColumns<FavoritePanelItem, FavoritePanelFilter>, columnName: string) => {
-    const column = columns.find(c => c.name === columnName);
-    return column && column.filters ? column.filters.filter(f => f.selected) : [];
-};
index 38229dff8390424fd26686da2158d6f59b22ceea..9e1b3ef1c20a3d27fdd1f46be629cfed77f85f37 100644 (file)
@@ -7,7 +7,7 @@ import { Dispatch } from "redux";
 import { RootState } from "../store";
 import { checkFavorite } from "./favorites-reducer";
 import { snackbarActions } from "../snackbar/snackbar-actions";
-import { ServiceRepository } from "../../services/services";
+import { ServiceRepository } from "~/services/services";
 
 export const favoritesActions = unionize({
     TOGGLE_FAVORITE: ofType<{ resourceUuid: string }>(),
index dfffb9cf0d843a2c9ac8d96f4e266f4ddf8cf05c..981b852fc606037be5fb2c40e3fb65fca659e361 100644 (file)
@@ -5,18 +5,18 @@
 import { Dispatch } from "redux";
 import { getProjectList, projectActions } from "../project/project-action";
 import { push } from "react-router-redux";
-import { TreeItemStatus } from "../../components/tree/tree";
+import { TreeItemStatus } from "~/components/tree/tree";
 import { findTreeItem } from "../project/project-reducer";
 import { RootState } from "../store";
-import { ResourceKind } from "../../models/resource";
+import { Resource, ResourceKind } from "~/models/resource";
 import { projectPanelActions } from "../project-panel/project-panel-action";
-import { getCollectionUrl } from "../../models/collection";
-import { getProjectUrl, ProjectResource } from "../../models/project";
-import { ProjectService } from "../../services/project-service/project-service";
-import { ServiceRepository } from "../../services/services";
+import { getCollectionUrl } from "~/models/collection";
+import { getProjectUrl, ProjectResource } from "~/models/project";
+import { ProjectService } from "~/services/project-service/project-service";
+import { ServiceRepository } from "~/services/services";
 import { sidePanelActions } from "../side-panel/side-panel-action";
 import { SidePanelIdentifiers } from "../side-panel/side-panel-reducer";
-import { getUuidObjectType, ObjectTypes } from "../../models/object-types";
+import { getUuidObjectType, ObjectTypes } from "~/models/object-types";
 
 export const getResourceUrl = (resourceKind: ResourceKind, resourceUuid: string): string => {
     switch (resourceKind) {
@@ -62,7 +62,6 @@ export const setProjectItem = (itemId: string, itemMode: ItemMode) =>
                     dispatch(projectPanelActions.RESET_PAGINATION());
                     dispatch(projectPanelActions.REQUEST_ITEMS());
                 }));
-
         } else {
             const uuid = services.authService.getUuid();
             if (itemId === uuid) {
index fd893a3c98c669eb499db444da0ecd3fb35af776..663add3e2eb68c9549aac05f334cd301a156535c 100644 (file)
@@ -3,19 +3,19 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { DataExplorerMiddlewareService } from "../data-explorer/data-explorer-middleware-service";
-import { ProjectPanelColumnNames, ProjectPanelFilter } from "../../views/project-panel/project-panel";
+import { ProjectPanelColumnNames, ProjectPanelFilter } from "~/views/project-panel/project-panel";
 import { RootState } from "../store";
-import { DataColumns } from "../../components/data-table/data-table";
-import { ServiceRepository } from "../../services/services";
-import { ProjectPanelItem, resourceToDataItem } from "../../views/project-panel/project-panel-item";
-import { SortDirection } from "../../components/data-table/data-column";
-import { OrderBuilder } from "../../common/api/order-builder";
-import { FilterBuilder } from "../../common/api/filter-builder";
-import { ProcessResource } from "../../models/process";
-import { GroupContentsResourcePrefix, GroupContentsResource } from "../../services/groups-service/groups-service";
+import { DataColumns } from "~/components/data-table/data-table";
+import { ServiceRepository } from "~/services/services";
+import { ProjectPanelItem, resourceToDataItem } from "~/views/project-panel/project-panel-item";
+import { SortDirection } from "~/components/data-table/data-column";
+import { OrderBuilder, OrderDirection } from "~/common/api/order-builder";
+import { FilterBuilder } from "~/common/api/filter-builder";
+import { GroupContentsResourcePrefix } from "~/services/groups-service/groups-service";
 import { checkPresenceInFavorites } from "../favorites/favorites-actions";
 import { projectPanelActions } from "./project-panel-action";
 import { Dispatch, MiddlewareAPI } from "redux";
+import { ProjectResource } from "~/models/project";
 
 export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService {
     constructor(private services: ServiceRepository, id: string) {
@@ -26,71 +26,53 @@ export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService
         const state = api.getState();
         const dataExplorer = state.dataExplorer[this.getId()];
         const columns = dataExplorer.columns as DataColumns<ProjectPanelItem, ProjectPanelFilter>;
-        const typeFilters = getColumnFilters(columns, ProjectPanelColumnNames.TYPE);
-        const statusFilters = getColumnFilters(columns, ProjectPanelColumnNames.STATUS);
-        const sortColumn = dataExplorer.columns.find(({ sortDirection }) => Boolean(sortDirection && sortDirection !== "none"));
-        const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC ? SortDirection.ASC : SortDirection.DESC;
-        if (typeFilters.length > 0) {
-            this.services.groupsService
-                .contents(state.projects.currentItemId, {
-                    limit: dataExplorer.rowsPerPage,
-                    offset: dataExplorer.page * dataExplorer.rowsPerPage,
-                    order: sortColumn
-                        ? sortColumn.name === ProjectPanelColumnNames.NAME
-                            ? getOrder("name", sortDirection)
-                            : getOrder("createdAt", sortDirection)
-                        : OrderBuilder.create(),
-                    filters: FilterBuilder
-                        .create()
-                        .concat(FilterBuilder
-                            .create()
-                            .addIsA("uuid", typeFilters.map(f => f.type)))
-                        .concat(FilterBuilder
-                            .create(GroupContentsResourcePrefix.PROCESS)
-                            .addIn("state", statusFilters.map(f => f.type)))
-                        .concat(getSearchFilter(dataExplorer.searchValue))
-                })
-                .then(response => {
-                    api.dispatch(projectPanelActions.SET_ITEMS({
-                        items: response.items.map(resourceToDataItem),
-                        itemsAvailable: response.itemsAvailable,
-                        page: Math.floor(response.offset / response.limit),
-                        rowsPerPage: response.limit
-                    }));
-                    api.dispatch<any>(checkPresenceInFavorites(response.items.map(item => item.uuid)));
-                });
-        } else {
-            api.dispatch(projectPanelActions.SET_ITEMS({
-                items: [],
-                itemsAvailable: 0,
-                page: 0,
-                rowsPerPage: dataExplorer.rowsPerPage
-            }));
-        }
-    }
-}
+        const typeFilters = this.getColumnFilters(columns, ProjectPanelColumnNames.TYPE);
+        const statusFilters = this.getColumnFilters(columns, ProjectPanelColumnNames.STATUS);
+        const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE);
 
-const getColumnFilters = (columns: DataColumns<ProjectPanelItem, ProjectPanelFilter>, columnName: string) => {
-    const column = columns.find(c => c.name === columnName);
-    return column && column.filters ? column.filters.filter(f => f.selected) : [];
-};
+        const order = new OrderBuilder<ProjectResource>();
 
-const getOrder = (attribute: "name" | "createdAt", direction: SortDirection) =>
-    [
-        OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.COLLECTION),
-        OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROCESS),
-        OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROJECT)
-    ].reduce((acc, b) =>
-        acc.concat(direction === SortDirection.ASC
-            ? b.addAsc(attribute)
-            : b.addDesc(attribute)), OrderBuilder.create());
+        if (sortColumn) {
+            const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC
+                ? OrderDirection.ASC
+                : OrderDirection.DESC;
 
-const getSearchFilter = (searchValue: string) =>
-    searchValue
-        ? [
-            FilterBuilder.create(GroupContentsResourcePrefix.COLLECTION),
-            FilterBuilder.create(GroupContentsResourcePrefix.PROCESS),
-            FilterBuilder.create(GroupContentsResourcePrefix.PROJECT)]
-            .reduce((acc, b) =>
-                acc.concat(b.addILike("name", searchValue)), FilterBuilder.create())
-        : FilterBuilder.create();
+            const columnName = sortColumn && sortColumn.name === ProjectPanelColumnNames.NAME ? "name" : "createdAt";
+            order
+                .addOrder(sortDirection, columnName, GroupContentsResourcePrefix.COLLECTION)
+                .addOrder(sortDirection, columnName, GroupContentsResourcePrefix.PROCESS)
+                .addOrder(sortDirection, columnName, GroupContentsResourcePrefix.PROJECT);
+        }
+
+        this.services.groupsService
+            .contents(state.projects.currentItemId, {
+                limit: dataExplorer.rowsPerPage,
+                offset: dataExplorer.page * dataExplorer.rowsPerPage,
+                order: order.getOrder(),
+                filters: new FilterBuilder()
+                    .addIsA("uuid", typeFilters.map(f => f.type))
+                    .addIn("state", statusFilters.map(f => f.type), GroupContentsResourcePrefix.PROCESS)
+                    .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.COLLECTION)
+                    .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROCESS)
+                    .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROJECT)
+                    .getFilters()
+            })
+            .then(response => {
+                api.dispatch(projectPanelActions.SET_ITEMS({
+                    items: response.items.map(resourceToDataItem),
+                    itemsAvailable: response.itemsAvailable,
+                    page: Math.floor(response.offset / response.limit),
+                    rowsPerPage: response.limit
+                }));
+                api.dispatch<any>(checkPresenceInFavorites(response.items.map(item => item.uuid)));
+            })
+            .catch(() => {
+                api.dispatch(projectPanelActions.SET_ITEMS({
+                    items: [],
+                    itemsAvailable: 0,
+                    page: 0,
+                    rowsPerPage: dataExplorer.rowsPerPage
+                }));
+            });
+    }
+}
index 9777dc73641101970f9c99f3b6d8fa997bd4901c..2017658916cbfe7f5bc408c5eaaea4d547e6cc20 100644 (file)
@@ -3,18 +3,23 @@
 // SPDX-License-Identifier: AGPL-3.0
 import { default as unionize, ofType, UnionOf } from "unionize";
 
-import { ProjectResource } from "../../models/project";
+import { ProjectResource } from "~/models/project";
 import { Dispatch } from "redux";
-import { FilterBuilder } from "../../common/api/filter-builder";
+import { FilterBuilder } from "~/common/api/filter-builder";
 import { RootState } from "../store";
 import { checkPresenceInFavorites } from "../favorites/favorites-actions";
-import { ServiceRepository } from "../../services/services";
+import { ServiceRepository } from "~/services/services";
+import { projectPanelActions } from "~/store/project-panel/project-panel-action";
+import { updateDetails } from "~/store/details-panel/details-panel-action";
 
 export const projectActions = unionize({
     OPEN_PROJECT_CREATOR: ofType<{ ownerUuid: string }>(),
     CLOSE_PROJECT_CREATOR: ofType<{}>(),
     CREATE_PROJECT: ofType<Partial<ProjectResource>>(),
     CREATE_PROJECT_SUCCESS: ofType<ProjectResource>(),
+    OPEN_PROJECT_UPDATER: ofType<{ uuid: string}>(),
+    CLOSE_PROJECT_UPDATER: ofType<{}>(),
+    UPDATE_PROJECT_SUCCESS: ofType<ProjectResource>(),
     REMOVE_PROJECT: ofType<string>(),
     PROJECTS_REQUEST: ofType<string>(),
     PROJECTS_SUCCESS: ofType<{ projects: ProjectResource[], parentItemId?: string }>(),
@@ -26,13 +31,15 @@ export const projectActions = unionize({
     value: 'payload'
 });
 
+export const PROJECT_FORM_NAME = 'projectEditDialog';
+
 export const getProjectList = (parentUuid: string = '') => 
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         dispatch(projectActions.PROJECTS_REQUEST(parentUuid));
         return services.projectService.list({
-            filters: FilterBuilder
-                .create()
+            filters: new FilterBuilder()
                 .addEqual("ownerUuid", parentUuid)
+                .getFilters()
         }).then(({ items: projects }) => {
             dispatch(projectActions.PROJECTS_SUCCESS({ projects, parentItemId: parentUuid }));
             dispatch<any>(checkPresenceInFavorites(projects.map(project => project.uuid)));
@@ -50,4 +57,17 @@ export const createProject = (project: Partial<ProjectResource>) =>
             .then(project => dispatch(projectActions.CREATE_PROJECT_SUCCESS(project)));
     };
 
+export const updateProject = (project: Partial<ProjectResource>) =>
+    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const { uuid } = getState().projects.updater;
+        return services.projectService
+            .update(uuid, project)
+            .then(project => {
+                dispatch(projectActions.UPDATE_PROJECT_SUCCESS(project));
+                dispatch(projectPanelActions.REQUEST_ITEMS());
+                dispatch<any>(getProjectList(project.ownerUuid));
+                dispatch<any>(updateDetails(project));
+            });
+    };
+
 export type ProjectAction = UnionOf<typeof projectActions>;
index 92274b3d281efba4886f8aff65687e4d6625e426..bb60e396946a588f8d93a1c1f7e6803461ba82eb 100644 (file)
@@ -4,8 +4,8 @@
 
 import { projectsReducer, getTreePath } from "./project-reducer";
 import { projectActions } from "./project-action";
-import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
-import { mockProjectResource } from "../../models/test-utils";
+import { TreeItem, TreeItemStatus } from "~/components/tree/tree";
+import { mockProjectResource } from "~/models/test-utils";
 
 describe('project-reducer', () => {
 
@@ -35,6 +35,10 @@ describe('project-reducer', () => {
             creator: {
                 opened: false,
                 ownerUuid: "",
+            },
+            updater: {
+                opened: false,
+                uuid: ''
             }
         });
     });
@@ -50,6 +54,7 @@ describe('project-reducer', () => {
             }],
             currentItemId: "1",
             creator: { opened: false, ownerUuid: "" },
+            updater: { opened: false, uuid: '' }
         };
         const project = {
             items: [{
@@ -61,6 +66,7 @@ describe('project-reducer', () => {
             }],
             currentItemId: "",
             creator: { opened: false, ownerUuid: "" },
+            updater: { opened: false, uuid: '' }
         };
 
         const state = projectsReducer(initialState, projectActions.RESET_PROJECT_TREE_ACTIVITY(initialState.items[0].id));
@@ -77,7 +83,8 @@ describe('project-reducer', () => {
                 status: TreeItemStatus.PENDING
             }],
             currentItemId: "1",
-            creator: { opened: false, ownerUuid: "" }
+            creator: { opened: false, ownerUuid: "" },
+            updater: { opened: false, uuid: '' }
         };
         const project = {
             items: [{
@@ -89,6 +96,7 @@ describe('project-reducer', () => {
             }],
             currentItemId: "1",
             creator: { opened: false, ownerUuid: "" },
+            updater: { opened: false, uuid: '' }
         };
 
         const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(initialState.items[0].id));
@@ -106,7 +114,8 @@ describe('project-reducer', () => {
                 status: TreeItemStatus.PENDING,
             }],
             currentItemId: "1",
-            creator: { opened: false, ownerUuid: "" }
+            creator: { opened: false, ownerUuid: "" },
+            updater: { opened: false, uuid: '' }
         };
         const project = {
             items: [{
@@ -118,6 +127,8 @@ describe('project-reducer', () => {
             }],
             currentItemId: "1",
             creator: { opened: false, ownerUuid: "" },
+            updater: { opened: false, uuid: '' }
+
         };
 
         const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(initialState.items[0].id));
index f5af23ab3a3f6e5501bcd115ab0bf994b79ab0e1..bb0748657ee1e64b3d28b5e8bc923fc062ca6ea9 100644 (file)
@@ -5,13 +5,14 @@
 import * as _ from "lodash";
 
 import { projectActions, ProjectAction } from "./project-action";
-import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
-import { ProjectResource } from "../../models/project";
+import { TreeItem, TreeItemStatus } from "~/components/tree/tree";
+import { ProjectResource } from "~/models/project";
 
 export type ProjectState = {
     items: Array<TreeItem<ProjectResource>>,
     currentItemId: string,
-    creator: ProjectCreator
+    creator: ProjectCreator,
+    updater: ProjectUpdater
 };
 
 interface ProjectCreator {
@@ -20,6 +21,11 @@ interface ProjectCreator {
     error?: string;
 }
 
+interface ProjectUpdater {
+    opened: boolean;
+    uuid: string;
+}
+
 export function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
     let item;
     for (const t of tree) {
@@ -100,12 +106,24 @@ const updateCreator = (state: ProjectState, creator: Partial<ProjectCreator>) =>
     }
 });
 
+const updateProject = (state: ProjectState, updater?: Partial<ProjectUpdater>) => ({
+    ...state,
+    updater: {
+        ...state.updater,
+        ...updater
+    }
+});
+
 const initialState: ProjectState = {
     items: [],
     currentItemId: "",
     creator: {
         opened: false,
         ownerUuid: ""
+    },
+    updater: {
+        opened: false,
+        uuid: ''
     }
 };
 
@@ -116,6 +134,9 @@ export const projectsReducer = (state: ProjectState = initialState, action: Proj
         CLOSE_PROJECT_CREATOR: () => updateCreator(state, { opened: false }),
         CREATE_PROJECT: () => updateCreator(state, { error: undefined }),
         CREATE_PROJECT_SUCCESS: () => updateCreator(state, { opened: false, ownerUuid: "" }),
+        OPEN_PROJECT_UPDATER: ({ uuid }) => updateProject(state, { uuid, opened: true }),
+        CLOSE_PROJECT_UPDATER: () => updateProject(state, { opened: false, uuid: "" }),
+        UPDATE_PROJECT_SUCCESS: () => updateProject(state, { opened: false, uuid: "" }),
         REMOVE_PROJECT: () => state,
         PROJECTS_REQUEST: itemId => {
             const items = _.cloneDeep(state.items);
index e517fc8509d4175859b6ed3381baf7868442c67d..4872a72cf750486565eeaee5f8a36b94f4dc9233 100644 (file)
@@ -4,7 +4,7 @@
 
 import { sidePanelReducer } from "./side-panel-reducer";
 import { sidePanelActions } from "./side-panel-action";
-import { ProjectsIcon } from "../../components/icon/icon";
+import { ProjectsIcon } from "~/components/icon/icon";
 
 describe('side-panel-reducer', () => {
 
index 08231def6642588d305ba9242f9e3f056b5ba118..bda6965e2d7e65597a3cea46690df4e0275ddc8b 100644 (file)
@@ -4,8 +4,8 @@
 
 import * as _ from "lodash";
 import { sidePanelActions, SidePanelAction } from './side-panel-action';
-import { SidePanelItem } from '../../components/side-panel/side-panel';
-import { ProjectsIcon, ShareMeIcon, WorkflowIcon, RecentIcon, FavoriteIcon, TrashIcon } from "../../components/icon/icon";
+import { SidePanelItem } from '~/components/side-panel/side-panel';
+import { ProjectsIcon, ShareMeIcon, WorkflowIcon, RecentIcon, FavoriteIcon, TrashIcon } from "~/components/icon/icon";
 import { Dispatch } from "redux";
 import { push } from "react-router-redux";
 import { favoritePanelActions } from "../favorite-panel/favorite-panel-action";
index 0002a6d2fa00c7956fc83c8cf367c35d3649bca4..a4bf9d6e3b9692d00e92bfcdc59ec93fa66c89bb 100644 (file)
@@ -26,7 +26,7 @@ import { FavoritePanelMiddlewareService } from "./favorite-panel/favorite-panel-
 import { CollectionPanelState, collectionPanelReducer } from './collection-panel/collection-panel-reducer';
 import { DialogState, dialogReducer } from './dialog/dialog-reducer';
 import { CollectionsState, collectionsReducer } from './collections/collections-reducer';
-import { ServiceRepository } from "../services/services";
+import { ServiceRepository } from "~/services/services";
 import { treePickerReducer } from './tree-picker/tree-picker-reducer';
 import { TreePicker } from './tree-picker/tree-picker';
 
index 772d89dfed334651b7ab5fa2101092dab526c6eb..e3bebe1c858f6e9a6ef7017de35b6162e72a745d 100644 (file)
@@ -3,7 +3,6 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { default as unionize, ofType, UnionOf } from "unionize";
-import { TreeNode } from "../../models/tree";
 import { TreePickerNode } from "./tree-picker";
 
 export const treePickerActions = unionize({
index ac4de0c015984078a261680ca122bfb0c8783ba6..3248cb2efba7f06af804c0b4f6a169de02170a67 100644 (file)
@@ -2,12 +2,11 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { createTree, getNodeValue, getNodeChildren } from "../../models/tree";
+import { createTree, getNodeValue, getNodeChildren } from "~/models/tree";
 import { TreePickerNode, createTreePickerNode } from "./tree-picker";
 import { treePickerReducer } from "./tree-picker-reducer";
 import { treePickerActions } from "./tree-picker-actions";
-import { TreeItemStatus } from "../../components/tree/tree";
-
+import { TreeItemStatus } from "~/components/tree/tree";
 
 describe('TreePickerReducer', () => {
     it('LOAD_TREE_PICKER_NODE - initial state', () => {
index d195a98a6836b3f861768b703494e4c7ec5a783b..8d61714cc9f744929587dd7f96f613ba18120f67 100644 (file)
@@ -2,11 +2,10 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { createTree, setNodeValueWith, TreeNode, setNode, mapTree, mapTreeValues } from "../../models/tree";
+import { createTree, setNodeValueWith, TreeNode, setNode, mapTreeValues } from "~/models/tree";
 import { TreePicker, TreePickerNode } from "./tree-picker";
 import { treePickerActions, TreePickerAction } from "./tree-picker-actions";
-import { TreeItemStatus } from "../../components/tree/tree";
-
+import { TreeItemStatus } from "~/components/tree/tree";
 
 export const treePickerReducer = (state: TreePicker = createTree(), action: TreePickerAction) =>
     treePickerActions.match(action, {
@@ -50,4 +49,4 @@ const createTreeNode = (parent: string) => (node: TreePickerNode): TreeNode<Tree
     id: node.id,
     parent,
     value: node
-});
\ No newline at end of file
+});
index ee45beccd5451b11621dd136ad41ec183a23b6fa..e19ce3a7a48c0d162c1fb462c00ddb26d8b30c57 100644 (file)
@@ -2,8 +2,8 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { Tree } from "../../models/tree";
-import { TreeItemStatus } from "../../components/tree/tree";
+import { Tree } from "~/models/tree";
+import { TreeItemStatus } from "~/components/tree/tree";
 
 export type TreePicker = Tree<TreePickerNode>;
 
@@ -20,4 +20,4 @@ export const createTreePickerNode = (data: {id: string, value: any}) => ({
     selected: false,
     collapsed: true,
     status: TreeItemStatus.INITIAL
-});
\ No newline at end of file
+});
diff --git a/src/validators/create-collection/create-collection-validator.tsx b/src/validators/create-collection/create-collection-validator.tsx
deleted file mode 100644 (file)
index 2d8e1f5..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { require } from '../require';
-import { maxLength } from '../max-length';
-
-export const COLLECTION_NAME_VALIDATION = [require, maxLength(255)];
-export const COLLECTION_DESCRIPTION_VALIDATION = [maxLength(255)];
\ No newline at end of file
diff --git a/src/validators/create-project/create-project-validator.tsx b/src/validators/create-project/create-project-validator.tsx
deleted file mode 100644 (file)
index ddea8be..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { require } from '../require';
-import { maxLength } from '../max-length';
-
-export const PROJECT_NAME_VALIDATION = [require, maxLength(255)];
-export const PROJECT_DESCRIPTION_VALIDATION = [maxLength(255)];
-export const COLLECTION_NAME_VALIDATION = [require, maxLength(255)];
-export const COLLECTION_DESCRIPTION_VALIDATION = [maxLength(255)];
-export const COLLECTION_PROJECT_VALIDATION = [require];
index fdeb8fa8747118ea3c8bec26c473ef9e9e932f1c..edd07822942ace10ac40ae68e79b9222422dbe55 100644 (file)
@@ -6,4 +6,11 @@ import { require } from './require';
 import { maxLength } from './max-length';
 
 export const TAG_KEY_VALIDATION = [require, maxLength(255)];
-export const TAG_VALUE_VALIDATION = [require, maxLength(255)];
\ No newline at end of file
+export const TAG_VALUE_VALIDATION = [require, maxLength(255)];
+
+export const PROJECT_NAME_VALIDATION = [require, maxLength(255)];
+export const PROJECT_DESCRIPTION_VALIDATION = [maxLength(255)];
+
+export const COLLECTION_NAME_VALIDATION = [require, maxLength(255)];
+export const COLLECTION_DESCRIPTION_VALIDATION = [maxLength(255)];
+export const COLLECTION_PROJECT_VALIDATION = [require];
\ No newline at end of file
index 0ae41c657e34b925acb909084b848b9bb4cce31c..3dc6d1a1acf51370c3c7b9c169745dd98ae184d8 100644 (file)
@@ -5,10 +5,10 @@
 import { Redirect, RouteProps } from "react-router";
 import * as React from "react";
 import { connect, DispatchProp } from "react-redux";
-import { getUserDetails, saveApiToken } from "../../store/auth/auth-action";
-import { getProjectList } from "../../store/project/project-action";
-import { getUrlParameter } from "../../common/url";
-import { AuthService } from "../../services/auth-service/auth-service";
+import { getUserDetails, saveApiToken } from "~/store/auth/auth-action";
+import { getProjectList } from "~/store/project/project-action";
+import { getUrlParameter } from "~/common/url";
+import { AuthService } from "~/services/auth-service/auth-service";
 
 interface ApiTokenProps {
     authService: AuthService;
index 0009fc0d54f760bc9b171aa9eadcc1a0ca4046f9..ae9b53e33034355dcafd0f48013f9ad761c78656 100644 (file)
@@ -3,17 +3,17 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { connect } from "react-redux";
-import { CollectionPanelFiles as Component, CollectionPanelFilesProps } from "../../components/collection-panel-files/collection-panel-files";
-import { RootState } from "../../store/store";
-import { TreeItemStatus, TreeItem } from "../../components/tree/tree";
-import { CollectionPanelFilesState, CollectionPanelDirectory, CollectionPanelFile } from "../../store/collection-panel/collection-panel-files/collection-panel-files-state";
-import { FileTreeData } from "../../components/file-tree/file-tree-data";
+import { CollectionPanelFiles as Component, CollectionPanelFilesProps } from "~/components/collection-panel-files/collection-panel-files";
+import { RootState } from "~/store/store";
+import { TreeItemStatus, TreeItem } from "~/components/tree/tree";
+import { CollectionPanelFilesState, CollectionPanelDirectory, CollectionPanelFile } from "~/store/collection-panel/collection-panel-files/collection-panel-files-state";
+import { FileTreeData } from "~/components/file-tree/file-tree-data";
 import { Dispatch } from "redux";
-import { collectionPanelFilesAction } from "../../store/collection-panel/collection-panel-files/collection-panel-files-actions";
-import { contextMenuActions } from "../../store/context-menu/context-menu-actions";
+import { collectionPanelFilesAction } from "~/store/collection-panel/collection-panel-files/collection-panel-files-actions";
+import { contextMenuActions } from "~/store/context-menu/context-menu-actions";
 import { ContextMenuKind } from "../context-menu/context-menu";
-import { Tree, getNodeChildren, getNode } from "../../models/tree";
-import { CollectionFileType } from "../../models/collection-file";
+import { Tree, getNodeChildren, getNode } from "~/models/tree";
+import { CollectionFileType } from "~/models/collection-file";
 
 const memoizedMapStateToProps = () => {
     let prevState: CollectionPanelFilesState;
index 10da9ef183448299f8a695f175be28fe1d4c4687..4561f9d308879b981c8a9a72dc32c29dc701ddcc 100644 (file)
@@ -4,10 +4,10 @@
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
 import { ToggleFavoriteAction } from "../actions/favorite-action";
-import { toggleFavorite } from "../../../store/favorites/favorites-actions";
-import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, ProvenanceGraphIcon, AdvancedIcon, RemoveIcon } from "../../../components/icon/icon";
-import { openUpdater } from "../../../store/collections/updater/collection-updater-action";
-import { favoritePanelActions } from "../../../store/favorite-panel/favorite-panel-action";
+import { toggleFavorite } from "~/store/favorites/favorites-actions";
+import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, ProvenanceGraphIcon, AdvancedIcon, RemoveIcon } from "~/components/icon/icon";
+import { openUpdater } from "~/store/collections/updater/collection-updater-action";
+import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
 
 export const collectionActionSet: ContextMenuActionSet = [[
     {
index 91fa2b00f6d8e3f7e9ca2d4d3bbd1a7ffd933966..0bed68e917739eb875fc4e015c556547659f6dd8 100644 (file)
@@ -3,9 +3,9 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
-import { collectionPanelFilesAction } from "../../../store/collection-panel/collection-panel-files/collection-panel-files-actions";
-import { openMultipleFilesRemoveDialog } from "../../file-remove-dialog/multiple-files-remove-dialog";
-import { createCollectionWithSelected } from "../../create-collection-dialog-with-selected/create-collection-dialog-with-selected";
+import { collectionPanelFilesAction } from "~/store/collection-panel/collection-panel-files/collection-panel-files-actions";
+import { openMultipleFilesRemoveDialog } from "~/views-components/file-remove-dialog/multiple-files-remove-dialog";
+import { createCollectionWithSelected } from "~/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected";
 
 
 export const collectionFilesActionSet: ContextMenuActionSet = [[{
index e24108f4d88754fa30e22760e2d993cde0ae6b7f..8728ad31e19702674fb718711855bc959c1000c3 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
-import { RenameIcon, DownloadIcon, RemoveIcon } from "../../../components/icon/icon";
+import { RenameIcon, DownloadIcon, RemoveIcon } from "~/components/icon/icon";
 import { openRenameFileDialog } from "../../rename-file-dialog/rename-file-dialog";
 import { openFileRemoveDialog } from "../../file-remove-dialog/file-remove-dialog";
 
index e6356bbce093e3493099afaa750581e3f157d52a..7d8364bd70ea22b4c29bb69c5c11b95699f6765b 100644 (file)
@@ -4,10 +4,10 @@
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
 import { ToggleFavoriteAction } from "../actions/favorite-action";
-import { toggleFavorite } from "../../../store/favorites/favorites-actions";
-import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, RemoveIcon } from "../../../components/icon/icon";
-import { openUpdater } from "../../../store/collections/updater/collection-updater-action";
-import { favoritePanelActions } from "../../../store/favorite-panel/favorite-panel-action";
+import { toggleFavorite } from "~/store/favorites/favorites-actions";
+import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, RemoveIcon } from "~/components/icon/icon";
+import { openUpdater } from "~/store/collections/updater/collection-updater-action";
+import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
 
 export const collectionResourceActionSet: ContextMenuActionSet = [[
     {
index 72c72fa9cb0c6f44eecc548399877eedccfb06ce..7942937fb3f42c736e1abc0842ec71c224bd2482 100644 (file)
@@ -4,8 +4,8 @@
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
 import { ToggleFavoriteAction } from "../actions/favorite-action";
-import { toggleFavorite } from "../../../store/favorites/favorites-actions";
-import { favoritePanelActions } from "../../../store/favorite-panel/favorite-panel-action";
+import { toggleFavorite } from "~/store/favorites/favorites-actions";
+import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
 
 export const favoriteActionSet: ContextMenuActionSet = [[{
     component: ToggleFavoriteAction,
index 4e7a60028877f3d2fc0117a2d8a6e77c2f2073b0..1b000c88fcee77ec2c39a844d3476001fca725a7 100644 (file)
@@ -2,28 +2,39 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { reset } from "redux-form";
+import { reset, initialize } from "redux-form";
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
-import { projectActions } from "../../../store/project/project-action";
-import { NewProjectIcon } from "../../../components/icon/icon";
+import { projectActions, PROJECT_FORM_NAME } from "~/store/project/project-action";
+import { NewProjectIcon, RenameIcon } from "~/components/icon/icon";
 import { ToggleFavoriteAction } from "../actions/favorite-action";
-import { toggleFavorite } from "../../../store/favorites/favorites-actions";
-import { favoritePanelActions } from "../../../store/favorite-panel/favorite-panel-action";
+import { toggleFavorite } from "~/store/favorites/favorites-actions";
+import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
 import { PROJECT_CREATE_DIALOG } from "../../dialog-create/dialog-project-create";
 
-export const projectActionSet: ContextMenuActionSet = [[{
-    icon: NewProjectIcon,
-    name: "New project",
-    execute: (dispatch, resource) => {
-        dispatch(reset(PROJECT_CREATE_DIALOG));
-        dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
+export const projectActionSet: ContextMenuActionSet = [[
+    {
+        icon: NewProjectIcon,
+        name: "New project",
+        execute: (dispatch, resource) => {
+            dispatch(reset(PROJECT_CREATE_DIALOG));
+            dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
+        }
+    },
+    {
+        icon: RenameIcon,
+        name: "Edit project",
+        execute: (dispatch, resource) => {
+            dispatch(projectActions.OPEN_PROJECT_UPDATER({ uuid: resource.uuid }));
+            dispatch(initialize(PROJECT_FORM_NAME, { name: resource.name, description: resource.description }));
+        }
+    },
+    {
+        component: ToggleFavoriteAction,
+        execute: (dispatch, resource) => {
+            dispatch<any>(toggleFavorite(resource)).then(() => {
+                dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
+            });
+        }
     }
-}, {
-    component: ToggleFavoriteAction,
-    execute: (dispatch, resource) => {
-        dispatch<any>(toggleFavorite(resource)).then(() => {
-            dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
-        });
-    }
-}]];
+]];
index 9585a865e7405bed40b0725654bf0d9edd4fda69..f7d1c4e9bec62f1facc52495e6e6b6a35e93b800 100644 (file)
@@ -4,7 +4,7 @@
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
 import { ToggleFavoriteAction } from "../actions/favorite-action";
-import { toggleFavorite } from "../../../store/favorites/favorites-actions";
+import { toggleFavorite } from "~/store/favorites/favorites-actions";
 
 export const resourceActionSet: ContextMenuActionSet = [[{
     component: ToggleFavoriteAction,
index c88b1b44bd699097da3eb6b08b4fb0d82c092280..de3b954f4c1c819c5d9aab4a136cd0e03a377a6a 100644 (file)
@@ -5,11 +5,11 @@
 import { reset } from "redux-form";
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
-import { projectActions } from "../../../store/project/project-action";
-import { collectionCreateActions } from "../../../store/collections/creator/collection-creator-action";
+import { projectActions } from "~/store/project/project-action";
+import { collectionCreateActions } from "~/store/collections/creator/collection-creator-action";
 import { PROJECT_CREATE_DIALOG } from "../../dialog-create/dialog-project-create";
 import { COLLECTION_CREATE_DIALOG } from "../../dialog-create/dialog-collection-create";
-import { NewProjectIcon, CollectionIcon } from "../../../components/icon/icon";
+import { NewProjectIcon, CollectionIcon } from "~/components/icon/icon";
 
 export const rootProjectActionSet: ContextMenuActionSet =  [[
     {
@@ -19,7 +19,7 @@ export const rootProjectActionSet: ContextMenuActionSet =  [[
             dispatch(reset(PROJECT_CREATE_DIALOG));
             dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
         }
-    }, 
+    },
     {
         icon: CollectionIcon,
         name: "New Collection",
index 55fe8cfdba19efe1c42b088e7ccc87471aaea97f..21f037d9e5451fd3a0267ece7ddc1db6f9e3c8fc 100644 (file)
@@ -4,9 +4,9 @@
 
 import * as React from "react";
 import { ListItemIcon, ListItemText } from "@material-ui/core";
-import { AddFavoriteIcon, RemoveFavoriteIcon } from "../../../components/icon/icon";
+import { AddFavoriteIcon, RemoveFavoriteIcon } from "~/components/icon/icon";
 import { connect } from "react-redux";
-import { RootState } from "../../../store/store";
+import { RootState } from "~/store/store";
 
 const mapStateToProps = (state: RootState) => ({
     isFavorite: state.contextMenu.resource !== undefined && state.favorites[state.contextMenu.resource.uuid] === true
index 089580c2bdd38bdeac44eaa268700de18298c98f..cbcc0b72f201b76190ea4e7384af8d3433512ba6 100644 (file)
@@ -3,8 +3,8 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { Dispatch } from "redux";
-import { ContextMenuItem } from "../../components/context-menu/context-menu";
-import { ContextMenuResource } from "../../store/context-menu/context-menu-reducer";
+import { ContextMenuItem } from "~/components/context-menu/context-menu";
+import { ContextMenuResource } from "~/store/context-menu/context-menu-reducer";
 
 export interface ContextMenuAction extends ContextMenuItem {
     execute(dispatch: Dispatch, resource: ContextMenuResource): void;
index 0a629b2e476360a5046d77a422249a377d350374..8036bb572859ee90a11fa94474a328ec69aca07f 100644 (file)
@@ -3,11 +3,11 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { connect } from "react-redux";
-import { RootState } from "../../store/store";
-import { contextMenuActions } from "../../store/context-menu/context-menu-actions";
-import { ContextMenu as ContextMenuComponent, ContextMenuProps, ContextMenuItem } from "../../components/context-menu/context-menu";
-import { createAnchorAt } from "../../components/popover/helpers";
-import { ContextMenuResource } from "../../store/context-menu/context-menu-reducer";
+import { RootState } from "~/store/store";
+import { contextMenuActions } from "~/store/context-menu/context-menu-actions";
+import { ContextMenu as ContextMenuComponent, ContextMenuProps, ContextMenuItem } from "~/components/context-menu/context-menu";
+import { createAnchorAt } from "~/components/popover/helpers";
+import { ContextMenuResource } from "~/store/context-menu/context-menu-reducer";
 import { ContextMenuActionSet, ContextMenuAction } from "./context-menu-action-set";
 import { Dispatch } from "redux";
 
index 8a2efca208c239bfdf14eeb759059b1da8fbd296..46bc724d7704fdaa067640f8a17675774788d4c7 100644 (file)
@@ -4,8 +4,8 @@
 
 import { Dispatch } from "redux";
 import { reduxForm, reset, startSubmit, stopSubmit } from "redux-form";
-import { withDialog } from "../../store/dialog/with-dialog";
-import { dialogActions } from "../../store/dialog/dialog-actions";
+import { withDialog } from "~/store/dialog/with-dialog";
+import { dialogActions } from "~/store/dialog/dialog-actions";
 import { DialogCollectionCreateWithSelected } from "../dialog-create/dialog-collection-create-selected";
 import { loadProjectTreePickerProjects } from "../project-tree-picker/project-tree-picker";
 
index 9bb469aac16f28841ef2df15a36c7c2e040b5620..94eb82f94e8c89b5aaf354c1c11930d4af8679a9 100644 (file)
@@ -6,12 +6,12 @@ import { connect } from "react-redux";
 import { Dispatch } from "redux";
 import { SubmissionError } from "redux-form";
 
-import { RootState } from "../../store/store";
+import { RootState } from "~/store/store";
 import { DialogCollectionCreate } from "../dialog-create/dialog-collection-create";
-import { collectionCreateActions, createCollection } from "../../store/collections/creator/collection-creator-action";
-import { snackbarActions } from "../../store/snackbar/snackbar-actions";
-import { UploadFile } from "../../store/collections/uploader/collection-uploader-actions";
-import { projectPanelActions } from "../../store/project-panel/project-panel-action";
+import { collectionCreateActions, createCollection } from "~/store/collections/creator/collection-creator-action";
+import { snackbarActions } from "~/store/snackbar/snackbar-actions";
+import { UploadFile } from "~/store/collections/uploader/collection-uploader-actions";
+import { projectPanelActions } from "~/store/project-panel/project-panel-action";
 
 const mapStateToProps = (state: RootState) => ({
     open: state.collections.creator.opened
index aa0dc7bc89a49ea3584806556d268435a05e737d..43f56ed8944b660ebd3d3f1544119605d90ec931 100644 (file)
@@ -6,11 +6,11 @@ import { connect } from "react-redux";
 import { Dispatch } from "redux";
 import { SubmissionError } from "redux-form";
 
-import { RootState } from "../../store/store";
+import { RootState } from "~/store/store";
 import { DialogProjectCreate } from "../dialog-create/dialog-project-create";
-import { projectActions, createProject, getProjectList } from "../../store/project/project-action";
-import { projectPanelActions } from "../../store/project-panel/project-panel-action";
-import { snackbarActions } from "../../store/snackbar/snackbar-actions";
+import { projectActions, createProject, getProjectList } from "~/store/project/project-action";
+import { projectPanelActions } from "~/store/project-panel/project-panel-action";
+import { snackbarActions } from "~/store/snackbar/snackbar-actions";
 
 const mapStateToProps = (state: RootState) => ({
     open: state.projects.creator.opened
index fe5f850c77fe75c757977bea6cba7d76453dd833..fca9f05982b6ed559875a7bfeb252e8d4b8f1e5c 100644 (file)
@@ -4,7 +4,7 @@
 
 import * as React from 'react';
 import { Dialog, DialogActions, DialogTitle, DialogContent, WithStyles, withStyles, StyleRulesCallback, Button, Typography, Paper } from '@material-ui/core';
-import { ArvadosTheme } from '../../common/custom-theme';
+import { ArvadosTheme } from '~/common/custom-theme';
 
 type CssRules = 'link' | 'paper' | 'button';
 
@@ -27,7 +27,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 });
 
 interface CurrentTokenDataProps {
-    currentToken?: string; 
+    currentToken?: string;
     open: boolean;
 }
 
@@ -37,9 +37,9 @@ interface CurrentTokenActionProps {
 
 type CurrentTokenProps = CurrentTokenDataProps & CurrentTokenActionProps & WithStyles<CssRules>;
 
-export const CurrentTokenDialog = withStyles(styles)(    
+export const CurrentTokenDialog = withStyles(styles)(
     class extends React.Component<CurrentTokenProps> {
-        
+
         render() {
             const { classes, open, handleClose, currentToken } = this.props;
             return (
@@ -47,7 +47,7 @@ export const CurrentTokenDialog = withStyles(styles)(
                     <DialogTitle>Current Token</DialogTitle>
                     <DialogContent>
                         <Typography variant='body1' paragraph={true}>
-                            The Arvados API token is a secret key that enables the Arvados SDKs to access Arvados with the proper permissions. 
+                            The Arvados API token is a secret key that enables the Arvados SDKs to access Arvados with the proper permissions.
                             <Typography component='p'>
                                 For more information see
                                 <a href='http://doc.arvados.org/user/reference/api-tokens.html' target='blank' className={classes.link}>
@@ -56,13 +56,13 @@ export const CurrentTokenDialog = withStyles(styles)(
                             </Typography>
                         </Typography>
 
-                        <Typography variant='body1' paragraph={true}> 
+                        <Typography variant='body1' paragraph={true}>
                             Paste the following lines at a shell prompt to set up the necessary environment for Arvados SDKs to authenticate to your klingenc account.
                         </Typography>
 
                         <Paper className={classes.paper} elevation={0}>
                             <Typography variant='body1'>
-                                HISTIGNORE=$HISTIGNORE:'export ARVADOS_API_TOKEN=*'                            
+                                HISTIGNORE=$HISTIGNORE:'export ARVADOS_API_TOKEN=*'
                             </Typography>
                             <Typography variant='body1'>
                                 export ARVADOS_API_TOKEN={currentToken}
@@ -75,8 +75,8 @@ export const CurrentTokenDialog = withStyles(styles)(
                             </Typography>
                         </Paper>
                         <Typography variant='body1'>
-                            Arvados 
-                            <a href='http://doc.arvados.org/user/reference/api-tokens.html' target='blank' className={classes.link}>virtual machines</a> 
+                            Arvados
+                            <a href='http://doc.arvados.org/user/reference/api-tokens.html' target='blank' className={classes.link}>virtual machines</a>
                             do this for you automatically. This setup is needed only when you use the API remotely (e.g., from your own workstation).
                         </Typography>
                     </DialogContent>
@@ -87,4 +87,4 @@ export const CurrentTokenDialog = withStyles(styles)(
             );
         }
     }
-);
\ No newline at end of file
+);
index e13e8af0ec44934369a0ae5db0fdcb6b46987b2f..68eeb3c144210dbbb247e94f634014d8d3726a98 100644 (file)
@@ -3,14 +3,14 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { connect } from "react-redux";
-import { RootState } from "../../store/store";
-import { DataExplorer as DataExplorerComponent } from "../../components/data-explorer/data-explorer";
-import { getDataExplorer } from "../../store/data-explorer/data-explorer-reducer";
+import { RootState } from "~/store/store";
+import { DataExplorer as DataExplorerComponent } from "~/components/data-explorer/data-explorer";
+import { getDataExplorer } from "~/store/data-explorer/data-explorer-reducer";
 import { Dispatch } from "redux";
-import { dataExplorerActions } from "../../store/data-explorer/data-explorer-action";
-import { DataColumn } from "../../components/data-table/data-column";
-import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
-import { DataColumns } from "../../components/data-table/data-table";
+import { dataExplorerActions } from "~/store/data-explorer/data-explorer-action";
+import { DataColumn } from "~/components/data-table/data-column";
+import { DataTableFilterItem } from "~/components/data-table-filters/data-table-filters";
+import { DataColumns } from "~/components/data-table/data-table";
 
 interface Props {
     id: string;
index 2b99f023a92516ad670b0869f2f545dde02d823e..1b07642ab727da755b898553ebc29a768f6bc025 100644 (file)
@@ -5,10 +5,10 @@
 import * as React from 'react';
 import { Grid, Typography } from '@material-ui/core';
 import { FavoriteStar } from '../favorite-star/favorite-star';
-import { ResourceKind } from '../../models/resource';
-import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon } from '../../components/icon/icon';
-import { formatDate, formatFileSize } from '../../common/formatters';
-import { resourceLabel } from '../../common/labels';
+import { ResourceKind } from '~/models/resource';
+import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon } from '~/components/icon/icon';
+import { formatDate, formatFileSize } from '~/common/formatters';
+import { resourceLabel } from '~/common/labels';
 
 
 export const renderName = (item: {name: string; uuid: string, kind: string}) =>
@@ -64,4 +64,4 @@ export const renderType = (type: string) =>
 export const renderStatus = (item: {status?: string}) =>
     <Typography noWrap align="center" >
         {item.status || "-"}
-    </Typography>;
\ No newline at end of file
+    </Typography>;
index f0be4497ec6a2075b150c55657f59fca90da713a..c41e0b80ee1616af2b97f20d3d5c71e2311b0288 100644 (file)
@@ -3,13 +3,13 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { CollectionIcon } from '../../components/icon/icon';
-import { CollectionResource } from '../../models/collection';
-import { formatDate } from '../../common/formatters';
-import { resourceLabel } from '../../common/labels';
-import { ResourceKind } from '../../models/resource';
+import { CollectionIcon } from '~/components/icon/icon';
+import { CollectionResource } from '~/models/collection';
+import { formatDate } from '~/common/formatters';
+import { resourceLabel } from '~/common/labels';
+import { ResourceKind } from '~/models/resource';
 import { DetailsData } from "./details-data";
-import { DetailsAttribute } from "../../components/details-attribute/details-attribute";
+import { DetailsAttribute } from "~/components/details-attribute/details-attribute";
 
 export class CollectionDetails extends DetailsData<CollectionResource> {
 
index d20269cd77ca3e949a286a92fccd477b4be3a9b1..5c061883f9e0b6d57179cdc57a155f2a6fa90a3c 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { DetailsResource } from "../../models/details";
+import { DetailsResource } from "~/models/details";
 
 export abstract class DetailsData<T extends DetailsResource = DetailsResource> {
     constructor(protected item: T) {}
index 20d3843f7787a8fbd4aa23bbe91b1abfaacd6b81..a298d670ee70de01e14ab58b03c9410aaccd4e69 100644 (file)
@@ -5,21 +5,21 @@
 import * as React from 'react';
 import { Drawer, IconButton, Tabs, Tab, Typography, Grid } from '@material-ui/core';
 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
-import { ArvadosTheme } from '../../common/custom-theme';
+import { ArvadosTheme } from '~/common/custom-theme';
 import * as classnames from "classnames";
 import { connect } from 'react-redux';
-import { RootState } from '../../store/store';
-import { detailsPanelActions } from "../../store/details-panel/details-panel-action";
-import { CloseIcon } from '../../components/icon/icon';
-import { EmptyResource } from '../../models/empty';
+import { RootState } from '~/store/store';
+import { detailsPanelActions } from "~/store/details-panel/details-panel-action";
+import { CloseIcon } from '~/components/icon/icon';
+import { EmptyResource } from '~/models/empty';
 import { Dispatch } from "redux";
-import { ResourceKind } from "../../models/resource";
+import { ResourceKind } from "~/models/resource";
 import { ProjectDetails } from "./project-details";
 import { CollectionDetails } from "./collection-details";
 import { ProcessDetails } from "./process-details";
 import { EmptyDetails } from "./empty-details";
 import { DetailsData } from "./details-data";
-import { DetailsResource } from "../../models/details";
+import { DetailsResource } from "~/models/details";
 
 type CssRules = 'drawerPaper' | 'container' | 'opened' | 'headerContainer' | 'headerIcon' | 'tabContainer';
 
index 47cb4030eddad0f8e11c901d05b90e65072d686e..76778d72b45583b3c374b1bf3416e3528eef5e87 100644 (file)
@@ -3,10 +3,10 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { DefaultIcon, ProjectsIcon } from '../../components/icon/icon';
-import { EmptyResource } from '../../models/empty';
+import { DefaultIcon, ProjectsIcon } from '~/components/icon/icon';
+import { EmptyResource } from '~/models/empty';
 import { DetailsData } from "./details-data";
-import { DefaultView } from '../../components/default-view/default-view';
+import { DefaultView } from '~/components/default-view/default-view';
 
 export class EmptyDetails extends DetailsData<EmptyResource> {
     getIcon(className?: string) {
index e195d05f34d96fb2af1dad415c6852b604436830..dee6e8b0b9a20db979ca64cddda58b268c2535d1 100644 (file)
@@ -3,13 +3,13 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { ProcessIcon } from '../../components/icon/icon';
-import { ProcessResource } from '../../models/process';
-import { formatDate } from '../../common/formatters';
-import { ResourceKind } from '../../models/resource';
-import { resourceLabel } from '../../common/labels';
+import { ProcessIcon } from '~/components/icon/icon';
+import { ProcessResource } from '~/models/process';
+import { formatDate } from '~/common/formatters';
+import { ResourceKind } from '~/models/resource';
+import { resourceLabel } from '~/common/labels';
 import { DetailsData } from "./details-data";
-import { DetailsAttribute } from "../../components/details-attribute/details-attribute";
+import { DetailsAttribute } from "~/components/details-attribute/details-attribute";
 
 export class ProcessDetails extends DetailsData<ProcessResource> {
 
index b46bdcdbe85f312579169e816a172ea7a121f5de..154f0a2c906e908660d05d5d437c65ad7890911a 100644 (file)
@@ -3,13 +3,13 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { ProjectIcon } from '../../components/icon/icon';
-import { ProjectResource } from '../../models/project';
-import { formatDate } from '../../common/formatters';
-import { ResourceKind } from '../../models/resource';
-import { resourceLabel } from '../../common/labels';
+import { ProjectIcon } from '~/components/icon/icon';
+import { ProjectResource } from '~/models/project';
+import { formatDate } from '~/common/formatters';
+import { ResourceKind } from '~/models/resource';
+import { resourceLabel } from '~/common/labels';
 import { DetailsData } from "./details-data";
-import { DetailsAttribute } from "../../components/details-attribute/details-attribute";
+import { DetailsAttribute } from "~/components/details-attribute/details-attribute";
 
 export class ProjectDetails extends DetailsData<ProjectResource> {
 
index 5069db94b521999953524a3cffbdff1b2c5aacbc..af2536df9512a66b89a1d013a5345c6b579bb690 100644 (file)
@@ -4,10 +4,10 @@
 
 import * as React from "react";
 import { InjectedFormProps, Field, WrappedFieldProps } from "redux-form";
-import { Dialog, DialogTitle, DialogContent, DialogActions, Button, CircularProgress, FormHelperText } from "@material-ui/core";
-import { WithDialogProps } from "../../store/dialog/with-dialog";
-import { TextField } from "../../components/text-field/text-field";
-import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION, COLLECTION_PROJECT_VALIDATION } from "../../validators/create-project/create-project-validator";
+import { Dialog, DialogTitle, DialogContent, DialogActions, Button, CircularProgress } from "@material-ui/core";
+import { WithDialogProps } from "~/store/dialog/with-dialog";
+import { TextField } from "~/components/text-field/text-field";
+import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION, COLLECTION_PROJECT_VALIDATION } from "~/validators/validators";
 import { ProjectTreePicker } from "../project-tree-picker/project-tree-picker";
 
 export const DialogCollectionCreateWithSelected = (props: WithDialogProps<string> & InjectedFormProps<{ name: string }>) =>
index 226b6460e6e2e88485b9cec8a920883657071a57..af0e33f1b4260fab01a15bef29fef1985701ecdd 100644 (file)
@@ -5,17 +5,17 @@
 import * as React from 'react';
 import { reduxForm, Field } from 'redux-form';
 import { compose } from 'redux';
-import { TextField } from '../../components/text-field/text-field';
+import { TextField } from '~/components/text-field/text-field';
 import { Dialog, DialogActions, DialogContent, DialogTitle } from '@material-ui/core/';
 import { Button, StyleRulesCallback, WithStyles, withStyles, CircularProgress } from '@material-ui/core';
 
-import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION } from '../../validators/create-collection/create-collection-validator';
-import { FileUpload } from "../../components/file-upload/file-upload";
+import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION } from '~/validators/validators';
+import { FileUpload } from "~/components/file-upload/file-upload";
 import { connect, DispatchProp } from "react-redux";
-import { RootState } from "../../store/store";
-import { collectionUploaderActions, UploadFile } from "../../store/collections/uploader/collection-uploader-actions";
+import { RootState } from "~/store/store";
+import { collectionUploaderActions, UploadFile } from "~/store/collections/uploader/collection-uploader-actions";
 
-type CssRules = "button" | "lastButton" | "formContainer" | "textField" | "createProgress" | "dialogActions";
+type CssRules = "button" | "lastButton" | "formContainer" | "createProgress" | "dialogActions";
 
 const styles: StyleRulesCallback<CssRules> = theme => ({
     button: {
@@ -29,9 +29,6 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
         display: "flex",
         flexDirection: "column",
     },
-    textField: {
-        marginBottom: theme.spacing.unit * 3
-    },
     createProgress: {
         position: "absolute",
         minWidth: "20px",
@@ -42,10 +39,8 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
     }
 });
 
-interface DialogCollectionCreateProps {
+interface DialogCollectionDataProps {
     open: boolean;
-    handleClose: () => void;
-    onSubmit: (data: { name: string, description: string }, files: UploadFile[]) => void;
     handleSubmit: any;
     submitting: boolean;
     invalid: boolean;
@@ -53,6 +48,13 @@ interface DialogCollectionCreateProps {
     files: UploadFile[];
 }
 
+interface DialogCollectionActionProps {
+    handleClose: () => void;
+    onSubmit: (data: { name: string, description: string }, files: UploadFile[]) => void;
+}
+
+type DialogCollectionProps = DialogCollectionDataProps & DialogCollectionActionProps & DispatchProp & WithStyles<CssRules>;
+
 export const COLLECTION_CREATE_DIALOG = "collectionCreateDialog";
 
 export const DialogCollectionCreate = compose(
@@ -61,7 +63,7 @@ export const DialogCollectionCreate = compose(
     })),
     reduxForm({ form: COLLECTION_CREATE_DIALOG }),
     withStyles(styles))(
-        class DialogCollectionCreate extends React.Component<DialogCollectionCreateProps & DispatchProp & WithStyles<CssRules>> {
+    class DialogCollectionCreate extends React.Component<DialogCollectionProps> {
             render() {
                 const { classes, open, handleClose, handleSubmit, onSubmit, submitting, invalid, pristine, files } = this.props;
                 const busy = submitting || files.reduce(
@@ -82,13 +84,11 @@ export const DialogCollectionCreate = compose(
                                     disabled={submitting}
                                     component={TextField}
                                     validate={COLLECTION_NAME_VALIDATION}
-                                    className={classes.textField}
                                     label="Collection Name" />
                                 <Field name="description"
                                     disabled={submitting}
                                     component={TextField}
                                     validate={COLLECTION_DESCRIPTION_VALIDATION}
-                                    className={classes.textField}
                                     label="Description - optional" />
                                 <FileUpload
                                     files={files}
index 50e4c9807fac6efc91aabe2d985bd0325a6bc1c0..e77114b369a2137d5cdb3584703c68e5163e45f3 100644 (file)
@@ -5,13 +5,13 @@
 import * as React from 'react';
 import { reduxForm, Field } from 'redux-form';
 import { compose } from 'redux';
-import { TextField } from '../../components/text-field/text-field';
+import { TextField } from '~/components/text-field/text-field';
 import { Dialog, DialogActions, DialogContent, DialogTitle } from '@material-ui/core/';
 import { Button, StyleRulesCallback, WithStyles, withStyles, CircularProgress } from '@material-ui/core';
 
-import { PROJECT_NAME_VALIDATION, PROJECT_DESCRIPTION_VALIDATION } from '../../validators/create-project/create-project-validator';
+import { PROJECT_NAME_VALIDATION, PROJECT_DESCRIPTION_VALIDATION } from '~/validators/validators';
 
-type CssRules = "button" | "lastButton" | "formContainer" | "textField" | "dialog" | "dialogTitle" | "createProgress" | "dialogActions";
+type CssRules = "button" | "lastButton" | "formContainer" | "dialog" | "dialogTitle" | "createProgress" | "dialogActions";
 
 const styles: StyleRulesCallback<CssRules> = theme => ({
     button: {
@@ -29,9 +29,6 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
     dialogTitle: {
         paddingBottom: "0"
     },
-    textField: {
-        marginTop: "32px",
-    },
     dialog: {
         minWidth: "600px",
         minHeight: "320px"
@@ -78,12 +75,10 @@ export const DialogProjectCreate = compose(
                                 <Field name="name"
                                        component={TextField}
                                        validate={PROJECT_NAME_VALIDATION}
-                                       className={classes.textField}
                                        label="Project Name"/>
                                 <Field name="description"
                                        component={TextField}
                                        validate={PROJECT_DESCRIPTION_VALIDATION}
-                                       className={classes.textField}
                                        label="Description - optional"/>
                             </DialogContent>
                             <DialogActions className={classes.dialogActions}>
index f3f79fb62e1fad07b2936315cc5041dece75f3a9..18c43f2d008a1cbd407410d2389e3de8532a9fd0 100644 (file)
@@ -5,12 +5,13 @@
 import * as React from 'react';
 import { reduxForm, Field } from 'redux-form';
 import { compose } from 'redux';
-import { ArvadosTheme } from '../../common/custom-theme';
-import { Dialog, DialogActions, DialogContent, DialogTitle, TextField, StyleRulesCallback, withStyles, WithStyles, Button, CircularProgress } from '@material-ui/core';
-import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION } from '../../validators/create-collection/create-collection-validator';
-import { COLLECTION_FORM_NAME } from '../../store/collections/updater/collection-updater-action';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { Dialog, DialogActions, DialogContent, DialogTitle, StyleRulesCallback, withStyles, WithStyles, Button, CircularProgress } from '@material-ui/core';
+import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION } from '~/validators/validators';
+import { COLLECTION_FORM_NAME } from '~/store/collections/updater/collection-updater-action';
+import { TextField } from '~/components/text-field/text-field';
 
-type CssRules = 'content' | 'actions' | 'textField' | 'buttonWrapper' | 'saveButton' | 'circularProgress';
+type CssRules = 'content' | 'actions' | 'buttonWrapper' | 'saveButton' | 'circularProgress';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     content: {
@@ -22,9 +23,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         padding: `${theme.spacing.unit}px ${theme.spacing.unit * 3 - theme.spacing.unit / 2}px 
                 ${theme.spacing.unit * 3}px ${theme.spacing.unit * 3}px`
     },
-    textField: {
-        marginBottom: theme.spacing.unit * 3
-    },
     buttonWrapper: {
         position: 'relative'
     },
@@ -56,14 +54,6 @@ interface DialogCollectionAction {
 
 type DialogCollectionProps = DialogCollectionDataProps & DialogCollectionAction & WithStyles<CssRules>;
 
-interface TextFieldProps {
-    label: string;
-    floatinglabeltext: string;
-    className?: string;
-    input?: string;
-    meta?: any;
-}
-
 export const DialogCollectionUpdate = compose(
     reduxForm({ form: COLLECTION_FORM_NAME }),
     withStyles(styles))(
@@ -83,19 +73,15 @@ export const DialogCollectionUpdate = compose(
                         <form onSubmit={handleSubmit((data: any) => onSubmit(data))}>
                             <DialogTitle>Edit Collection</DialogTitle>
                             <DialogContent className={classes.content}>
-                                <Field name="name"
+                                <Field name='name'
                                     disabled={submitting}
-                                    component={this.renderTextField}
-                                    floatinglabeltext="Collection Name"
+                                    component={TextField}
                                     validate={COLLECTION_NAME_VALIDATION}
-                                    className={classes.textField}
                                     label="Collection Name" />
-                                <Field name="description"
+                                <Field name='description'
                                     disabled={submitting}
-                                    component={this.renderTextField}
-                                    floatinglabeltext="Description - optional"
+                                    component={TextField}
                                     validate={COLLECTION_DESCRIPTION_VALIDATION}
-                                    className={classes.textField}
                                     label="Description - optional" />
                             </DialogContent>
                             <DialogActions className={classes.actions}>
@@ -115,17 +101,5 @@ export const DialogCollectionUpdate = compose(
                     </Dialog>
                 );
             }
-
-            renderTextField = ({ input, label, meta: { touched, error }, ...custom }: TextFieldProps) => (
-                <TextField
-                    helperText={touched && error}
-                    label={label}
-                    className={this.props.classes.textField}
-                    error={touched && !!error}
-                    autoComplete='off'
-                    {...input}
-                    {...custom}
-                />
-            )
         }
     );
diff --git a/src/views-components/dialog-update/dialog-project-update.tsx b/src/views-components/dialog-update/dialog-project-update.tsx
new file mode 100644 (file)
index 0000000..5dde00a
--- /dev/null
@@ -0,0 +1,101 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { reduxForm, Field } from 'redux-form';
+import { compose } from 'redux';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { StyleRulesCallback, WithStyles, withStyles, Dialog, DialogTitle, DialogContent, DialogActions, CircularProgress, Button } from '../../../node_modules/@material-ui/core';
+import { TextField } from '~/components/text-field/text-field';
+import { PROJECT_FORM_NAME } from '~/store/project/project-action';
+import { PROJECT_NAME_VALIDATION, PROJECT_DESCRIPTION_VALIDATION } from '~/validators/validators';
+
+type CssRules = 'content' | 'actions' | 'buttonWrapper' | 'saveButton' | 'circularProgress';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    content: {
+        display: 'flex',
+        flexDirection: 'column'
+    },
+    actions: {
+        margin: 0,
+        padding: `${theme.spacing.unit}px ${theme.spacing.unit * 3 - theme.spacing.unit / 2}px 
+                ${theme.spacing.unit * 3}px ${theme.spacing.unit * 3}px`
+    },
+    buttonWrapper: {
+        position: 'relative'
+    },
+    saveButton: {
+        boxShadow: 'none'
+    },
+    circularProgress: {
+        position: 'absolute',
+        top: 0,
+        bottom: 0,
+        left: 0,
+        right: 0,
+        margin: 'auto'
+    }
+});
+
+interface DialogProjectDataProps {
+    open: boolean;
+    handleSubmit: any;
+    submitting: boolean;
+    invalid: boolean;
+    pristine: boolean;
+}
+
+interface DialogProjectActionProps {
+    handleClose: () => void;
+    onSubmit: (data: { name: string, description: string }) => void;
+}
+
+type DialogProjectProps = DialogProjectDataProps & DialogProjectActionProps & WithStyles<CssRules>;
+
+export const DialogProjectUpdate = compose(
+    reduxForm({ form: PROJECT_FORM_NAME }),
+    withStyles(styles))(
+
+        class DialogProjectUpdate extends React.Component<DialogProjectProps> {
+            render() {
+                const { handleSubmit, handleClose, onSubmit, open, classes, submitting, invalid, pristine } = this.props;
+                return <Dialog open={open}
+                    onClose={handleClose}
+                    fullWidth={true}
+                    maxWidth='sm'
+                    disableBackdropClick={true}
+                    disableEscapeKeyDown={true}>
+                    <form onSubmit={handleSubmit((data: any) => onSubmit(data))}>
+                        <DialogTitle>Edit Collection</DialogTitle>
+                        <DialogContent className={classes.content}>
+                            <Field name='name' 
+                                disabled={submitting}
+                                component={TextField}
+                                validate={PROJECT_NAME_VALIDATION}
+                                label="Project Name" />
+                            <Field name='description' 
+                                disabled={submitting}
+                                component={TextField} 
+                                validate={PROJECT_DESCRIPTION_VALIDATION}
+                                label="Description - optional" />
+                        </DialogContent>
+                        <DialogActions className={classes.actions}>
+                            <Button onClick={handleClose} color="primary"
+                                disabled={submitting}>CANCEL</Button>
+                            <div className={classes.buttonWrapper}>
+                                <Button type="submit" className={classes.saveButton}
+                                    color="primary"
+                                    disabled={invalid || submitting || pristine}
+                                    variant="contained">
+                                    SAVE
+                                </Button>
+                                {submitting && <CircularProgress size={20} className={classes.circularProgress} />}
+                            </div>
+                        </DialogActions>
+                    </form>
+                </Dialog>;
+            }
+        }
+    );
index f896e304999380412326cd1f01f1ebf78d52edb3..755cc67c265b52e0b80c12b04561f6b081bb989a 100644 (file)
@@ -3,9 +3,9 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from "react";
-import { FavoriteIcon } from "../../components/icon/icon";
+import { FavoriteIcon } from "~/components/icon/icon";
 import { connect } from "react-redux";
-import { RootState } from "../../store/store";
+import { RootState } from "~/store/store";
 import { withStyles, StyleRulesCallback, WithStyles } from "@material-ui/core";
 
 type CssRules = "icon";
@@ -24,4 +24,4 @@ const mapStateToProps = (state: RootState, props: { resourceUuid: string; classN
 export const FavoriteStar = connect(mapStateToProps)(
     withStyles(styles)((props: { visible: boolean; className?: string; } & WithStyles<CssRules>) =>
         props.visible ? <FavoriteIcon className={props.className || props.classes.icon} /> : null
-    ));
\ No newline at end of file
+    ));
index 3678e535450932c8d7e428e9da7280b49e2b00d4..04497933c87effdbe6b81ba540bd4adf7ae90f52 100644 (file)
@@ -4,10 +4,10 @@
 
 import { Dispatch } from "redux";
 import { connect } from "react-redux";
-import { ConfirmationDialog } from "../../components/confirmation-dialog/confirmation-dialog";
-import { withDialog } from "../../store/dialog/with-dialog";
-import { dialogActions } from "../../store/dialog/dialog-actions";
-import { snackbarActions } from "../../store/snackbar/snackbar-actions";
+import { ConfirmationDialog } from "~/components/confirmation-dialog/confirmation-dialog";
+import { withDialog } from "~/store/dialog/with-dialog";
+import { dialogActions } from "~/store/dialog/dialog-actions";
+import { snackbarActions } from "~/store/snackbar/snackbar-actions";
 
 const FILE_REMOVE_DIALOG = 'fileRemoveDialog';
 
@@ -35,4 +35,4 @@ export const openFileRemoveDialog = (fileId: string) =>
 
 export const [FileRemoveDialog] = [ConfirmationDialog]
     .map(withDialog(FILE_REMOVE_DIALOG))
-    .map(connect(undefined, mapDispatchToProps));
\ No newline at end of file
+    .map(connect(undefined, mapDispatchToProps));
index 8810e23a9128a1ed933c37d698f680b68300edf6..1362de6b8ac424c479c460674cb1eb43fe5996a7 100644 (file)
@@ -4,10 +4,10 @@
 
 import { Dispatch } from "redux";
 import { connect } from "react-redux";
-import { ConfirmationDialog } from "../../components/confirmation-dialog/confirmation-dialog";
-import { withDialog } from "../../store/dialog/with-dialog";
-import { dialogActions } from "../../store/dialog/dialog-actions";
-import { snackbarActions } from "../../store/snackbar/snackbar-actions";
+import { ConfirmationDialog } from "~/components/confirmation-dialog/confirmation-dialog";
+import { withDialog } from "~/store/dialog/with-dialog";
+import { dialogActions } from "~/store/dialog/dialog-actions";
+import { snackbarActions } from "~/store/snackbar/snackbar-actions";
 
 const MULTIPLE_FILES_REMOVE_DIALOG = 'multipleFilesRemoveDialog';
 
@@ -34,4 +34,4 @@ export const openMultipleFilesRemoveDialog = () =>
 
 export const [MultipleFilesRemoveDialog] = [ConfirmationDialog]
     .map(withDialog(MULTIPLE_FILES_REMOVE_DIALOG))
-    .map(connect(undefined, mapDispatchToProps));
\ No newline at end of file
+    .map(connect(undefined, mapDispatchToProps));
index a634d43955e47f581a259d64eb192ca72fb95055..75a39fd57ac74ecb72634ab7aadb563176670aaf 100644 (file)
@@ -3,14 +3,14 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from "react";
-import { mount, configure, ReactWrapper } from "enzyme";
+import { mount, configure } from "enzyme";
 import * as Adapter from "enzyme-adapter-react-16";
 import { MainAppBar } from "./main-app-bar";
-import { SearchBar } from "../../components/search-bar/search-bar";
-import { Breadcrumbs } from "../../components/breadcrumbs/breadcrumbs";
-import { DropdownMenu } from "../../components/dropdown-menu/dropdown-menu";
+import { SearchBar } from "~/components/search-bar/search-bar";
+import { Breadcrumbs } from "~/components/breadcrumbs/breadcrumbs";
+import { DropdownMenu } from "~/components/dropdown-menu/dropdown-menu";
 import { Button, MenuItem, IconButton } from "@material-ui/core";
-import { User } from "../../models/user";
+import { User } from "~/models/user";
 
 configure({ adapter: new Adapter() });
 
index 9c031080a77d3a1c392fdf78383cb501d7c2934e..8bce325425f7c52a07cbbb958c8f316b48cdb29f 100644 (file)
@@ -4,11 +4,11 @@
 
 import * as React from "react";
 import { AppBar, Toolbar, Typography, Grid, IconButton, Badge, Button, MenuItem } from "@material-ui/core";
-import { User, getUserFullname } from "../../models/user";
-import { SearchBar } from "../../components/search-bar/search-bar";
-import { Breadcrumbs, Breadcrumb } from "../../components/breadcrumbs/breadcrumbs";
-import { DropdownMenu } from "../../components/dropdown-menu/dropdown-menu";
-import { DetailsIcon, NotificationIcon, UserPanelIcon, HelpIcon } from "../../components/icon/icon";
+import { User, getUserFullname } from "~/models/user";
+import { SearchBar } from "~/components/search-bar/search-bar";
+import { Breadcrumbs, Breadcrumb } from "~/components/breadcrumbs/breadcrumbs";
+import { DropdownMenu } from "~/components/dropdown-menu/dropdown-menu";
+import { DetailsIcon, NotificationIcon, UserPanelIcon, HelpIcon } from "~/components/icon/icon";
 
 export interface MainAppBarMenuItem {
     label: string;
index 1c343a2dde1d8a4acf364d95f6f0a451508f5fee..9143c47a2da5efe25b4d983015946706a8487c7f 100644 (file)
@@ -7,15 +7,15 @@ import { Dispatch } from "redux";
 import { connect } from "react-redux";
 import { Typography } from "@material-ui/core";
 import { TreePicker } from "../tree-picker/tree-picker";
-import { TreeProps, TreeItem, TreeItemStatus } from "../../components/tree/tree";
-import { ProjectResource } from "../../models/project";
-import { treePickerActions } from "../../store/tree-picker/tree-picker-actions";
-import { ListItemTextIcon } from "../../components/list-item-text-icon/list-item-text-icon";
-import { ProjectIcon } from "../../components/icon/icon";
-import { createTreePickerNode } from "../../store/tree-picker/tree-picker";
-import { RootState } from "../../store/store";
-import { ServiceRepository } from "../../services/services";
-import { FilterBuilder } from "../../common/api/filter-builder";
+import { TreeProps, TreeItem, TreeItemStatus } from "~/components/tree/tree";
+import { ProjectResource } from "~/models/project";
+import { treePickerActions } from "~/store/tree-picker/tree-picker-actions";
+import { ListItemTextIcon } from "~/components/list-item-text-icon/list-item-text-icon";
+import { ProjectIcon } from "~/components/icon/icon";
+import { createTreePickerNode } from "~/store/tree-picker/tree-picker";
+import { RootState } from "~/store/store";
+import { ServiceRepository } from "~/services/services";
+import { FilterBuilder } from "~/common/api/filter-builder";
 
 type ProjectTreePickerProps = Pick<TreeProps<ProjectResource>, 'toggleItemActive' | 'toggleItemOpen'>;
 
@@ -48,9 +48,9 @@ export const loadProjectTreePickerProjects = (id: string) =>
 
         const ownerUuid = id.length === 0 ? services.authService.getUuid() || '' : id;
 
-        const filters = FilterBuilder
-            .create()
-            .addEqual('ownerUuid', ownerUuid);
+        const filters = new FilterBuilder()
+            .addEqual('ownerUuid', ownerUuid)
+            .getFilters();
 
         const { items } = await services.projectService.list({ filters });
 
@@ -72,4 +72,4 @@ const receiveProjectTreePickerData = (id: string, projects: ProjectResource[]) =
             nodes: projects.map(project => createTreePickerNode({ id: project.uuid, value: project }))
         }));
         dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id }));
-    };
\ No newline at end of file
+    };
index 98b4a67cb00049551f52da687981aa6a2258bd18..140119e17e6849ad57d397fa02f362b2f1d7111f 100644 (file)
@@ -11,9 +11,9 @@ import { Collapse } from '@material-ui/core';
 import CircularProgress from '@material-ui/core/CircularProgress';
 
 import { ProjectTree } from './project-tree';
-import { TreeItem } from '../../components/tree/tree';
-import { ProjectResource } from '../../models/project';
-import { mockProjectResource } from '../../models/test-utils';
+import { TreeItem } from '~/components/tree/tree';
+import { ProjectResource } from '~/models/project';
+import { mockProjectResource } from '~/models/test-utils';
 
 Enzyme.configure({ adapter: new Adapter() });
 
index c9d4c3e3a5e47d1a7f5b2971689b7e7203886db1..8c1ed33059b75bb1ad3056ad2c9f34414eae4374 100644 (file)
@@ -4,12 +4,12 @@
 
 import * as React from 'react';
 import { ReactElement } from 'react';
-import { StyleRulesCallback, Theme, WithStyles, withStyles } from '@material-ui/core/styles';
-import { Tree, TreeItem, TreeItemStatus } from '../../components/tree/tree';
-import { ProjectResource } from '../../models/project';
-import { ProjectIcon } from '../../components/icon/icon';
-import { ArvadosTheme } from '../../common/custom-theme';
-import { ListItemTextIcon } from '../../components/list-item-text-icon/list-item-text-icon';
+import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
+import { Tree, TreeItem, TreeItemStatus } from '~/components/tree/tree';
+import { ProjectResource } from '~/models/project';
+import { ProjectIcon } from '~/components/icon/icon';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { ListItemTextIcon } from '~/components/list-item-text-icon/list-item-text-icon';
 
 type CssRules = 'root';
 
index f08727f64cd95e8143783b05cc23e13088da8909..1bf76e49eaa7fafc0bcebf3165ceb82947adf6e8 100644 (file)
@@ -4,8 +4,8 @@
 
 import * as React from "react";
 import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from "@material-ui/core";
-import { withDialog } from "../../store/dialog/with-dialog";
-import { dialogActions } from "../../store/dialog/dialog-actions";
+import { withDialog } from "~/store/dialog/with-dialog";
+import { dialogActions } from "~/store/dialog/dialog-actions";
 
 export const REMOVE_DIALOG = 'removeCollectionFilesDialog';
 
index 5e2f7c44def1d38ecd584a80278f4f798d52ea38..37028f9db5adc5b4cffe7cfffa25b1d969a7fcee 100644 (file)
@@ -4,9 +4,9 @@
 
 import { Dispatch } from "redux";
 import { reduxForm, reset, startSubmit, stopSubmit } from "redux-form";
-import { withDialog } from "../../store/dialog/with-dialog";
-import { dialogActions } from "../../store/dialog/dialog-actions";
-import { RenameDialog } from "../../components/rename-dialog/rename-dialog";
+import { withDialog } from "~/store/dialog/with-dialog";
+import { dialogActions } from "~/store/dialog/dialog-actions";
+import { RenameDialog } from "~/components/rename-dialog/rename-dialog";
 
 export const RENAME_FILE_DIALOG = 'renameFileDialog';
 
index c65d3640473cd035cf9bb4edf4b90a88d3375307..535777e1bd4fd0765f1ac88914e592165f969258 100644 (file)
@@ -4,10 +4,10 @@
 
 import * as React from "react";
 import { connect } from "react-redux";
-import { RootState } from "../../store/store";
+import { RootState } from "~/store/store";
 import MaterialSnackbar, { SnackbarProps } from "@material-ui/core/Snackbar";
 import { Dispatch } from "redux";
-import { snackbarActions } from "../../store/snackbar/snackbar-actions";
+import { snackbarActions } from "~/store/snackbar/snackbar-actions";
 
 const mapStateToProps = (state: RootState): SnackbarProps => ({
     anchorOrigin: { vertical: "bottom", horizontal: "center" },
index 3e0fc6ed9407bf3c5b6e47da62708234daa65089..09a07443f26b9097ddcccfa9ded1b95d2dea5d85 100644 (file)
@@ -3,10 +3,10 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { connect } from "react-redux";
-import { Tree, TreeProps, TreeItem } from "../../components/tree/tree";
-import { RootState } from "../../store/store";
-import { TreePicker as TTreePicker, TreePickerNode, createTreePickerNode } from "../../store/tree-picker/tree-picker";
-import { getNodeValue, getNodeChildren } from "../../models/tree";
+import { Tree, TreeProps, TreeItem } from "~/components/tree/tree";
+import { RootState } from "~/store/store";
+import { TreePicker as TTreePicker, TreePickerNode, createTreePickerNode } from "~/store/tree-picker/tree-picker";
+import { getNodeValue, getNodeChildren } from "~/models/tree";
 
 const memoizedMapStateToProps = () => {
     let prevState: TTreePicker;
index 1374ac41bad7feb8f827b1055d63e5c56b50ff80..239df58952febb4f45c6a5609f6f0b6129691fe3 100644 (file)
@@ -5,11 +5,11 @@
 import { connect } from "react-redux";
 import { Dispatch } from "redux";
 import { SubmissionError } from "redux-form";
-import { RootState } from "../../store/store";
-import { snackbarActions } from "../../store/snackbar/snackbar-actions";
-import { collectionUpdaterActions, updateCollection } from "../../store/collections/updater/collection-updater-action";
-import { dataExplorerActions } from "../../store/data-explorer/data-explorer-action";
-import { PROJECT_PANEL_ID } from "../../views/project-panel/project-panel";
+import { RootState } from "~/store/store";
+import { snackbarActions } from "~/store/snackbar/snackbar-actions";
+import { collectionUpdaterActions, updateCollection } from "~/store/collections/updater/collection-updater-action";
+import { dataExplorerActions } from "~/store/data-explorer/data-explorer-action";
+import { PROJECT_PANEL_ID } from "~/views/project-panel/project-panel";
 import { DialogCollectionUpdate } from "../dialog-update/dialog-collection-update";
 
 const mapStateToProps = (state: RootState) => ({
diff --git a/src/views-components/update-project-dialog/update-project-dialog.tsx b/src/views-components/update-project-dialog/update-project-dialog.tsx
new file mode 100644 (file)
index 0000000..0ea23c8
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { connect } from "react-redux";
+import { Dispatch } from "redux";
+import { SubmissionError } from "redux-form";
+import { RootState } from "~/store/store";
+import { snackbarActions } from "~/store/snackbar/snackbar-actions";
+import { DialogProjectUpdate } from "../dialog-update/dialog-project-update";
+import { projectActions, updateProject } from "~/store/project/project-action";
+
+const mapStateToProps = (state: RootState) => ({
+    open: state.projects.updater.opened
+});
+
+const mapDispatchToProps = (dispatch: Dispatch) => ({
+    handleClose: () => {
+        dispatch(projectActions.CLOSE_PROJECT_UPDATER());
+    },
+    onSubmit: (data: { name: string, description: string }) => {
+        return dispatch<any>(editProject(data))
+            .catch((e: any) => {
+                if (e.errors) {
+                    throw new SubmissionError({ name: e.errors.join("").includes("UniqueViolation") ? "Project with this name already exists." : "" });
+                }
+            });
+    }
+});
+
+const editProject = (data: { name: string, description: string }) =>
+    (dispatch: Dispatch, getState: () => RootState) => {
+        const { uuid } = getState().projects.updater;
+        return dispatch<any>(updateProject(data)).then(() => {
+            dispatch(snackbarActions.OPEN_SNACKBAR({
+                message: "Project has been successfully updated.",
+                hideDuration: 2000
+            }));
+        });
+    };
+
+export const UpdateProjectDialog = connect(mapStateToProps, mapDispatchToProps)(DialogProjectUpdate);
index 489d28473b0803fe3887380c850186f05c707680..374cb95159483f5d4896fcbd539fddfc61931ea3 100644 (file)
@@ -9,16 +9,16 @@ import {
 } from '@material-ui/core';
 import { connect, DispatchProp } from "react-redux";
 import { RouteComponentProps } from 'react-router';
-import { ArvadosTheme } from '../../common/custom-theme';
-import { RootState } from '../../store/store';
-import { MoreOptionsIcon, CollectionIcon, CopyIcon } from '../../components/icon/icon';
-import { DetailsAttribute } from '../../components/details-attribute/details-attribute';
-import { CollectionResource } from '../../models/collection';
-import { CollectionPanelFiles } from '../../views-components/collection-panel-files/collection-panel-files';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { RootState } from '~/store/store';
+import { MoreOptionsIcon, CollectionIcon, CopyIcon } from '~/components/icon/icon';
+import { DetailsAttribute } from '~/components/details-attribute/details-attribute';
+import { CollectionResource } from '~/models/collection';
+import { CollectionPanelFiles } from '~/views-components/collection-panel-files/collection-panel-files';
 import * as CopyToClipboard from 'react-copy-to-clipboard';
-import { TagResource } from '../../models/tag';
+import { TagResource } from '~/models/tag';
 import { CollectionTagForm } from './collection-tag-form';
-import { deleteCollectionTag } from '../../store/collection-panel/collection-panel-action';
+import { deleteCollectionTag } from '~/store/collection-panel/collection-panel-action';
 
 type CssRules = 'card' | 'iconHeader' | 'tag' | 'copyIcon' | 'value';
 
@@ -60,11 +60,11 @@ type CollectionPanelProps = CollectionPanelDataProps & CollectionPanelActionProp
 
 
 export const CollectionPanel = withStyles(styles)(
-    connect((state: RootState) => ({ 
-        item: state.collectionPanel.item, 
-        tags: state.collectionPanel.tags 
+    connect((state: RootState) => ({
+        item: state.collectionPanel.item,
+        tags: state.collectionPanel.tags
     }))(
-        class extends React.Component<CollectionPanelProps> { 
+        class extends React.Component<CollectionPanelProps> {
 
             render() {
                 const { classes, item, tags, onContextMenu } = this.props;
@@ -84,8 +84,8 @@ export const CollectionPanel = withStyles(styles)(
                             <CardContent>
                                 <Grid container direction="column">
                                     <Grid item xs={6}>
-                                    <DetailsAttribute classValue={classes.value} 
-                                            label='Collection UUID' 
+                                    <DetailsAttribute classValue={classes.value}
+                                            label='Collection UUID'
                                             value={item && item.uuid}>
                                         <CopyToClipboard text={item && item.uuid}>
                                             <CopyIcon className={classes.copyIcon} />
@@ -100,7 +100,7 @@ export const CollectionPanel = withStyles(styles)(
                         </Card>
 
                         <Card className={classes.card}>
-                            <CardHeader title="Tags" />
+                            <CardHeader title="Properties" />
                             <CardContent>
                                 <Grid container direction="column">
                                     <Grid item xs={12}><CollectionTagForm /></Grid>
index 89cf880afab32a28d08d6b935b41f1964c91873c..83ad0ca42860a1e70c8e85900411af15eea3baf0 100644 (file)
@@ -5,21 +5,16 @@
 import * as React from 'react';
 import { reduxForm, Field, reset } from 'redux-form';
 import { compose, Dispatch } from 'redux';
-import { ArvadosTheme } from '../../common/custom-theme';
-import { StyleRulesCallback, withStyles, WithStyles, TextField, Button, CircularProgress } from '@material-ui/core';
-import { TagProperty } from '../../models/tag';
-import { createCollectionTag, COLLECTION_TAG_FORM_NAME } from '../../store/collection-panel/collection-panel-action';
-import { TAG_VALUE_VALIDATION, TAG_KEY_VALIDATION } from '../../validators/validators';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { StyleRulesCallback, withStyles, WithStyles, Button, CircularProgress, Grid } from '@material-ui/core';
+import { TagProperty } from '~/models/tag';
+import { TextField } from '~/components/text-field/text-field';
+import { createCollectionTag, COLLECTION_TAG_FORM_NAME } from '~/store/collection-panel/collection-panel-action';
+import { TAG_VALUE_VALIDATION, TAG_KEY_VALIDATION } from '~/validators/validators';
 
-type CssRules = 'form' | 'textField' | 'buttonWrapper' | 'saveButton' | 'circularProgress';
+type CssRules = 'buttonWrapper' | 'saveButton' | 'circularProgress';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
-    form: {
-        marginBottom: theme.spacing.unit * 4 
-    },
-    textField: {
-        marginRight: theme.spacing.unit
-    },
     buttonWrapper: {
         position: 'relative',
         display: 'inline-block'
@@ -47,72 +42,53 @@ interface CollectionTagFormActionProps {
     handleSubmit: any;
 }
 
-interface TextFieldProps {
-    label: string;
-    floatinglabeltext: string;
-    className?: string;
-    input?: string;
-    meta?: any;
-}
-
 type CollectionTagFormProps = CollectionTagFormDataProps & CollectionTagFormActionProps & WithStyles<CssRules>;
 
 export const CollectionTagForm = compose(
-    reduxForm({ 
-        form: COLLECTION_TAG_FORM_NAME, 
+    reduxForm({
+        form: COLLECTION_TAG_FORM_NAME,
         onSubmit: (data: TagProperty, dispatch: Dispatch) => {
             dispatch<any>(createCollectionTag(data));
             dispatch(reset(COLLECTION_TAG_FORM_NAME));
-        } 
+        }
     }),
     withStyles(styles))(
-        
-    class CollectionTagForm extends React.Component<CollectionTagFormProps> {
+
+        class CollectionTagForm extends React.Component<CollectionTagFormProps> {
 
             render() {
                 const { classes, submitting, pristine, invalid, handleSubmit } = this.props;
                 return (
-                    <form className={classes.form} onSubmit={handleSubmit}>
-                        <Field name="key"
-                            disabled={submitting}
-                            component={this.renderTextField}
-                            floatinglabeltext="Key"
-                            validate={TAG_KEY_VALIDATION}
-                            className={classes.textField}
-                            label="Key" />
-                        <Field name="value"
-                            disabled={submitting}
-                            component={this.renderTextField}
-                            floatinglabeltext="Value"
-                            validate={TAG_VALUE_VALIDATION}
-                            className={classes.textField}
-                            label="Value" />
-                        <div className={classes.buttonWrapper}>
-                            <Button type="submit" className={classes.saveButton}
-                                color="primary"
-                                size='small'
-                                disabled={invalid || submitting || pristine}
-                                variant="contained">
-                                ADD
-                            </Button>
-                            {submitting && <CircularProgress size={20} className={classes.circularProgress} />}
-                        </div>
+                    <form onSubmit={handleSubmit}>
+                        <Grid container justify="flex-start" alignItems="baseline" spacing={24}>
+                            <Grid item xs={3} component={"span"}>
+                                <Field name="key"
+                                    disabled={submitting}
+                                    component={TextField}
+                                    validate={TAG_KEY_VALIDATION}
+                                    label="Key" />
+                            </Grid>
+                            <Grid item xs={5} component={"span"}>
+                                <Field name="value"
+                                    disabled={submitting}
+                                    component={TextField}
+                                    validate={TAG_VALUE_VALIDATION}
+                                    label="Value" />
+                            </Grid>
+                            <Grid item component={"span"} className={classes.buttonWrapper}>
+                                <Button type="submit" className={classes.saveButton}
+                                    color="primary"
+                                    size='small'
+                                    disabled={invalid || submitting || pristine}
+                                    variant="contained">
+                                    ADD
+                                </Button>
+                                {submitting && <CircularProgress size={20} className={classes.circularProgress} />}
+                            </Grid>
+                        </Grid>
                     </form>
                 );
             }
-
-            renderTextField = ({ input, label, meta: { touched, error }, ...custom }: TextFieldProps) => (
-                <TextField
-                    helperText={touched && error}
-                    label={label}
-                    className={this.props.classes.textField}
-                    error={touched && !!error}
-                    autoComplete='off'
-                    {...input}
-                    {...custom}
-                />
-            )
-
         }
 
-    );
\ No newline at end of file
+    );
index da48298aa7ec6d2dd14fd6d44b3d0209e25ba4d7..842b6d6bbcc05469aa0566dbee43f6e4cbd608cd 100644 (file)
@@ -2,8 +2,8 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { GroupContentsResource } from "../../services/groups-service/groups-service";
-import { ResourceKind } from "../../models/resource";
+import { GroupContentsResource } from "~/services/groups-service/groups-service";
+import { ResourceKind } from "~/models/resource";
 
 export interface FavoritePanelItem {
     uuid: string;
@@ -16,7 +16,6 @@ export interface FavoritePanelItem {
     status?: string;
 }
 
-
 export function resourceToDataItem(r: GroupContentsResource): FavoritePanelItem {
     return {
         uuid: r.uuid,
@@ -28,4 +27,3 @@ export function resourceToDataItem(r: GroupContentsResource): FavoritePanelItem
         status:  r.kind === ResourceKind.PROCESS ? r.state : undefined
     };
 }
-
index a48395d4b2d03f83557f4ce46631580c96930689..125ea27ddf1635836bdd592091025d637efd028a 100644 (file)
@@ -5,20 +5,20 @@
 import * as React from 'react';
 import { FavoritePanelItem } from './favorite-panel-item';
 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
-import { DataExplorer } from "../../views-components/data-explorer/data-explorer";
+import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
 import { DispatchProp, connect } from 'react-redux';
-import { DataColumns } from '../../components/data-table/data-table';
+import { DataColumns } from '~/components/data-table/data-table';
 import { RouteComponentProps } from 'react-router';
-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 } from '../../models/resource';
-import { resourceLabel } from '../../common/labels';
-import { ArvadosTheme } from '../../common/custom-theme';
-import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '../../views-components/data-explorer/renderers';
-import { FAVORITE_PANEL_ID } from "../../store/favorite-panel/favorite-panel-action";
-import { FavoriteIcon } from '../../components/icon/icon';
+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 } from '~/models/resource';
+import { resourceLabel } from '~/common/labels';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '~/views-components/data-explorer/renderers';
+import { FAVORITE_PANEL_ID } from "~/store/favorite-panel/favorite-panel-action";
+import { FavoriteIcon } from '~/components/icon/icon';
 
 type CssRules = "toolbar" | "button";
 
@@ -51,6 +51,7 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
         selected: true,
         configurable: true,
         sortDirection: SortDirection.ASC,
+        filters: [],
         render: renderName,
         width: "450px"
     },
@@ -58,6 +59,7 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
         name: "Status",
         selected: true,
         configurable: true,
+        sortDirection: SortDirection.NONE,
         filters: [
             {
                 name: ContainerRequestState.COMMITTED,
@@ -82,6 +84,7 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
         name: FavoritePanelColumnNames.TYPE,
         selected: true,
         configurable: true,
+        sortDirection: SortDirection.NONE,
         filters: [
             {
                 name: resourceLabel(ResourceKind.COLLECTION),
@@ -106,6 +109,8 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
         name: FavoritePanelColumnNames.OWNER,
         selected: true,
         configurable: true,
+        sortDirection: SortDirection.NONE,
+        filters: [],
         render: item => renderOwner(item.owner),
         width: "200px"
     },
@@ -113,6 +118,8 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
         name: FavoritePanelColumnNames.FILE_SIZE,
         selected: true,
         configurable: true,
+        sortDirection: SortDirection.NONE,
+        filters: [],
         render: item => renderFileSize(item.fileSize),
         width: "50px"
     },
@@ -121,6 +128,7 @@ export const columns: DataColumns<FavoritePanelItem, FavoritePanelFilter> = [
         selected: true,
         configurable: true,
         sortDirection: SortDirection.NONE,
+        filters: [],
         render: item => renderDate(item.lastModified),
         width: "150px"
     }
@@ -151,7 +159,7 @@ export const FavoritePanel = withStyles(styles)(
                     onRowClick={this.props.onItemClick}
                     onRowDoubleClick={this.props.onItemDoubleClick}
                     onContextMenu={this.props.onContextMenu}
-                    extractKey={(item: FavoritePanelItem) => item.uuid} 
+                    extractKey={(item: FavoritePanelItem) => item.uuid}
                     defaultIcon={FavoriteIcon}
                     defaultMessages={['Your favorites list is empty.']}/>
                 ;
index d81ef5022cea599b174e4a4980b05ab738b4b633..f0318591f5066d388286040ea6ccd96fe1b431d0 100644 (file)
@@ -2,8 +2,8 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { GroupContentsResource } from "../../services/groups-service/groups-service";
-import { ResourceKind } from "../../models/resource";
+import { GroupContentsResource } from "~/services/groups-service/groups-service";
+import { ResourceKind } from "~/models/resource";
 
 export interface ProjectPanelItem {
     uuid: string;
@@ -17,7 +17,6 @@ export interface ProjectPanelItem {
     status?: string;
 }
 
-
 export function resourceToDataItem(r: GroupContentsResource): ProjectPanelItem {
     return {
         uuid: r.uuid,
@@ -30,4 +29,3 @@ export function resourceToDataItem(r: GroupContentsResource): ProjectPanelItem {
         status:  r.kind === ResourceKind.PROCESS ? r.state : undefined
     };
 }
-
index cf4aca5b6aacf074ebb7490a298d75f689a4903c..0f958d2cfbbb87684c47c1d77a16319f3e494e28 100644 (file)
@@ -5,20 +5,20 @@
 import * as React from 'react';
 import { ProjectPanelItem } from './project-panel-item';
 import { Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
-import { DataExplorer } from "../../views-components/data-explorer/data-explorer";
+import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
 import { DispatchProp, connect } from 'react-redux';
-import { DataColumns } from '../../components/data-table/data-table';
+import { DataColumns } from '~/components/data-table/data-table';
 import { RouteComponentProps } from 'react-router';
-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 } from '../../models/resource';
-import { resourceLabel } from '../../common/labels';
-import { ArvadosTheme } from '../../common/custom-theme';
-import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '../../views-components/data-explorer/renderers';
-import { restoreBranch } from '../../store/navigation/navigation-action';
-import { ProjectIcon } from '../../components/icon/icon';
+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 } from '~/models/resource';
+import { resourceLabel } from '~/common/labels';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '~/views-components/data-explorer/renderers';
+import { restoreBranch } from '~/store/navigation/navigation-action';
+import { ProjectIcon } from '~/components/icon/icon';
 
 type CssRules = 'root' | "toolbar" | "button";
 
@@ -56,6 +56,7 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
         selected: true,
         configurable: true,
         sortDirection: SortDirection.ASC,
+        filters: [],
         render: renderName,
         width: "450px"
     },
@@ -63,6 +64,7 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
         name: "Status",
         selected: true,
         configurable: true,
+        sortDirection: SortDirection.NONE,
         filters: [
             {
                 name: ContainerRequestState.COMMITTED,
@@ -87,6 +89,7 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
         name: ProjectPanelColumnNames.TYPE,
         selected: true,
         configurable: true,
+        sortDirection: SortDirection.NONE,
         filters: [
             {
                 name: resourceLabel(ResourceKind.COLLECTION),
@@ -111,6 +114,8 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
         name: ProjectPanelColumnNames.OWNER,
         selected: true,
         configurable: true,
+        sortDirection: SortDirection.NONE,
+        filters: [],
         render: item => renderOwner(item.owner),
         width: "200px"
     },
@@ -118,6 +123,8 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
         name: ProjectPanelColumnNames.FILE_SIZE,
         selected: true,
         configurable: true,
+        sortDirection: SortDirection.NONE,
+        filters: [],
         render: item => renderFileSize(item.fileSize),
         width: "50px"
     },
@@ -126,6 +133,7 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
         selected: true,
         configurable: true,
         sortDirection: SortDirection.NONE,
+        filters: [],
         render: item => renderDate(item.lastModified),
         width: "150px"
     }
index 588b6d9b8ca7f98db074e7fffb359d7630b633b2..d03e1172fb4fc963c99fcaaeb9ea014c9dda6751 100644 (file)
@@ -4,30 +4,27 @@
 
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
-import { Workbench } from '../../views/workbench/workbench';
+import { Workbench } from './workbench';
 import { Provider } from "react-redux";
-import { configureStore } from "../../store/store";
+import { configureStore } from "~/store/store";
 import createBrowserHistory from "history/createBrowserHistory";
 import { ConnectedRouter } from "react-router-redux";
 import { MuiThemeProvider } from '@material-ui/core/styles';
-import { CustomTheme } from '../../common/custom-theme';
-import { createServices } from "../../services/services";
-import { AuthService } from "../../services/auth-service/auth-service";
-import Axios from "axios";
+import { CustomTheme } from '~/common/custom-theme';
+import { createServices } from "~/services/services";
 
 const history = createBrowserHistory();
-const authService = new AuthService(Axios.create(), '/arvados/v1');
-
-authService.getUuid = jest.fn().mockReturnValueOnce('test');
 
 it('renders without crashing', () => {
     const div = document.createElement('div');
-    const store = configureStore(createBrowserHistory(), createServices("/arvados/v1"));
+    const services = createServices("/arvados/v1");
+       services.authService.getUuid = jest.fn().mockReturnValueOnce('test');
+    const store = configureStore(createBrowserHistory(), services);
     ReactDOM.render(
         <MuiThemeProvider theme={CustomTheme}>
             <Provider store={store}>
                 <ConnectedRouter history={history}>
-                    <Workbench authService={authService} />
+                    <Workbench authService={services.authService}/>
                 </ConnectedRouter>
             </Provider>
         </MuiThemeProvider>,
index 356a6706c65014d4e61a634052059800d13c9778..a38afb7ac30e32b8f06f265a1635693aead54ca9 100644 (file)
@@ -6,49 +6,50 @@ import * as React from 'react';
 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
 import Drawer from '@material-ui/core/Drawer';
 import { connect, DispatchProp } from "react-redux";
-import { Route, Switch, RouteComponentProps, Redirect } from "react-router";
-import { login, logout } from "../../store/auth/auth-action";
-import { User } from "../../models/user";
-import { RootState } from "../../store/store";
-import { MainAppBar, MainAppBarActionProps, MainAppBarMenuItem } from '../../views-components/main-app-bar/main-app-bar';
-import { Breadcrumb } from '../../components/breadcrumbs/breadcrumbs';
+import { Route, RouteComponentProps, Switch, Redirect } from "react-router";
+import { login, logout } from "~/store/auth/auth-action";
+import { User } from "~/models/user";
+import { RootState } from "~/store/store";
+import { MainAppBar, MainAppBarActionProps, MainAppBarMenuItem } from '~/views-components/main-app-bar/main-app-bar';
+import { Breadcrumb } from '~/components/breadcrumbs/breadcrumbs';
 import { push } from 'react-router-redux';
 import { reset } from 'redux-form';
-import { ProjectTree } from '../../views-components/project-tree/project-tree';
-import { TreeItem } from "../../components/tree/tree";
-import { getTreePath } from '../../store/project/project-reducer';
-import { sidePanelActions } from '../../store/side-panel/side-panel-action';
-import { SidePanel, SidePanelItem } from '../../components/side-panel/side-panel';
-import { ItemMode, setProjectItem } from "../../store/navigation/navigation-action";
-import { projectActions } from "../../store/project/project-action";
-import { collectionCreateActions } from '../../store/collections/creator/collection-creator-action';
-import { ProjectPanel } from "../project-panel/project-panel";
-import { DetailsPanel } from '../../views-components/details-panel/details-panel';
-import { ArvadosTheme } from '../../common/custom-theme';
-import { CreateProjectDialog } from "../../views-components/create-project-dialog/create-project-dialog";
+import { ProjectTree } from '~/views-components/project-tree/project-tree';
+import { TreeItem } from "~/components/tree/tree";
+import { getTreePath } from '~/store/project/project-reducer';
+import { sidePanelActions } from '~/store/side-panel/side-panel-action';
+import { SidePanel, SidePanelItem } from '~/components/side-panel/side-panel';
+import { ItemMode, setProjectItem } from "~/store/navigation/navigation-action";
+import { projectActions } from "~/store/project/project-action";
+import { collectionCreateActions } from '~/store/collections/creator/collection-creator-action';
+import { ProjectPanel } from "~/views/project-panel/project-panel";
+import { DetailsPanel } from '~/views-components/details-panel/details-panel';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { CreateProjectDialog } from "~/views-components/create-project-dialog/create-project-dialog";
 
-import { detailsPanelActions, loadDetails } from "../../store/details-panel/details-panel-action";
-import { contextMenuActions } from "../../store/context-menu/context-menu-actions";
-import { SidePanelIdentifiers } from '../../store/side-panel/side-panel-reducer';
-import { ProjectResource } from '../../models/project';
-import { ResourceKind } from '../../models/resource';
-import { ContextMenu, ContextMenuKind } from "../../views-components/context-menu/context-menu";
+import { detailsPanelActions, loadDetails } from "~/store/details-panel/details-panel-action";
+import { contextMenuActions } from "~/store/context-menu/context-menu-actions";
+import { SidePanelIdentifiers } from '~/store/side-panel/side-panel-reducer';
+import { ProjectResource } from '~/models/project';
+import { ResourceKind } from '~/models/resource';
+import { ContextMenu, ContextMenuKind } from "~/views-components/context-menu/context-menu";
 import { FavoritePanel } from "../favorite-panel/favorite-panel";
-import { CurrentTokenDialog } from '../../views-components/current-token-dialog/current-token-dialog';
-import { Snackbar } from '../../views-components/snackbar/snackbar';
-import { favoritePanelActions } from '../../store/favorite-panel/favorite-panel-action';
-import { CreateCollectionDialog } from '../../views-components/create-collection-dialog/create-collection-dialog';
+import { CurrentTokenDialog } from '~/views-components/current-token-dialog/current-token-dialog';
+import { Snackbar } from '~/views-components/snackbar/snackbar';
+import { favoritePanelActions } from '~/store/favorite-panel/favorite-panel-action';
+import { CreateCollectionDialog } from '~/views-components/create-collection-dialog/create-collection-dialog';
 import { CollectionPanel } from '../collection-panel/collection-panel';
-import { loadCollection, loadCollectionTags } from '../../store/collection-panel/collection-panel-action';
-import { getCollectionUrl } from '../../models/collection';
-import { UpdateCollectionDialog } from '../../views-components/update-collection-dialog/update-collection-dialog.';
-import { AuthService } from "../../services/auth-service/auth-service";
-import { RenameFileDialog } from '../../views-components/rename-file-dialog/rename-file-dialog';
-import { FileRemoveDialog } from '../../views-components/file-remove-dialog/file-remove-dialog';
-import { MultipleFilesRemoveDialog } from '../../views-components/file-remove-dialog/multiple-files-remove-dialog';
-import { DialogCollectionCreateWithSelectedFile } from '../../views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected';
-import { COLLECTION_CREATE_DIALOG } from '../../views-components/dialog-create/dialog-collection-create';
-import { PROJECT_CREATE_DIALOG } from '../../views-components/dialog-create/dialog-project-create';
+import { loadCollection, loadCollectionTags } from '~/store/collection-panel/collection-panel-action';
+import { getCollectionUrl } from '~/models/collection';
+import { UpdateCollectionDialog } from '~/views-components/update-collection-dialog/update-collection-dialog.';
+import { UpdateProjectDialog } from '~/views-components/update-project-dialog/update-project-dialog';
+import { AuthService } from "~/services/auth-service/auth-service";
+import { RenameFileDialog } from '~/views-components/rename-file-dialog/rename-file-dialog';
+import { FileRemoveDialog } from '~/views-components/file-remove-dialog/file-remove-dialog';
+import { MultipleFilesRemoveDialog } from '~/views-components/file-remove-dialog/multiple-files-remove-dialog';
+import { DialogCollectionCreateWithSelectedFile } from '~/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected';
+import { COLLECTION_CREATE_DIALOG } from '~/views-components/dialog-create/dialog-collection-create';
+import { PROJECT_CREATE_DIALOG } from '~/views-components/dialog-create/dialog-project-create';
 
 const DRAWER_WITDH = 240;
 const APP_BAR_HEIGHT = 100;
@@ -245,6 +246,7 @@ export const Workbench = withStyles(styles)(
                         <FileRemoveDialog />
                         <MultipleFilesRemoveDialog />
                         <UpdateCollectionDialog />
+                        <UpdateProjectDialog />
                         <CurrentTokenDialog
                             currentToken={this.props.currentToken}
                             open={this.state.isCurrentTokenDialogOpen}
@@ -253,7 +255,7 @@ export const Workbench = withStyles(styles)(
                 );
             }
 
-            renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel 
+            renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel
                 onItemRouteChange={(collectionId) => {
                     this.props.dispatch<any>(loadCollection(collectionId, ResourceKind.COLLECTION));
                     this.props.dispatch<any>(loadCollectionTags(collectionId));
@@ -280,7 +282,7 @@ export const Workbench = withStyles(styles)(
                     } else {
                         kind = ContextMenuKind.RESOURCE;
                     }
-                    
+
                     this.openContextMenu(event, {
                         uuid: item.uuid,
                         name: item.name,
@@ -298,7 +300,7 @@ export const Workbench = withStyles(styles)(
                         case ResourceKind.COLLECTION:
                             this.props.dispatch(loadCollection(item.uuid, item.kind as ResourceKind));
                             this.props.dispatch(push(getCollectionUrl(item.uuid)));
-                        default: 
+                        default:
                             this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE));
                             this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind));
                     }
index af933d9fa9a09755a64da1f8f3f8c4c5b54c1d68..b0e2455281bd09f74171637f30e6e7b22b351319 100644 (file)
@@ -1,6 +1,6 @@
 {
   "compilerOptions": {
-    "baseUrl": ".",
+    "baseUrl": "./",
     "outDir": "build/dist",
     "module": "esnext",
     "target": "es5",
     "suppressImplicitAnyIndexErrors": true,
     "noUnusedLocals": false,
     "experimentalDecorators": true,
-    "emitDecoratorMetadata": true
-    //  waits for moduleNameMapper being able to override
-    //    "paths": {
-    //      "~/*": ["src/*"]
-    //    }
+    "emitDecoratorMetadata": true,
+    "paths": {
+      "~/*": ["src/*"]
+    }
   },
   "exclude": [
     "node_modules",
index 700f4ad2b107eaa3d5ce8f1728a448d64f157343..ae2b1c571627921abf4fc87cee07613423376134 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -6226,9 +6226,9 @@ react-router@4.3.1, react-router@^4.2.0, react-router@^4.3.1:
     prop-types "^15.6.1"
     warning "^4.0.1"
 
-react-scripts-ts@2.16.0:
-  version "2.16.0"
-  resolved "https://registry.yarnpkg.com/react-scripts-ts/-/react-scripts-ts-2.16.0.tgz#45f831a12139c3b59d6bb729c1b6ef51e0f22908"
+react-scripts-ts@2.17.0:
+  version "2.17.0"
+  resolved "https://registry.yarnpkg.com/react-scripts-ts/-/react-scripts-ts-2.17.0.tgz#398bae19a30c9b39b3dfe0720ebb40c60c2f6574"
   dependencies:
     autoprefixer "7.1.6"
     babel-jest "^22.1.0"