From: Janicki Artur Date: Tue, 10 Jul 2018 11:54:09 +0000 (+0200) Subject: Merge branch 'master' into 13765-information-inside-details-panel X-Git-Tag: 1.2.0~54^2 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/f05e6a9cece7e3b118134136ee81bd7477ad10a0?hp=0e3c4c507b9d3ec37b808b7a38cf111722659403 Merge branch 'master' into 13765-information-inside-details-panel refs #13765 Arvados-DCO-1.1-Signed-off-by: Janicki Artur --- diff --git a/.env b/.env new file mode 100644 index 00000000..13aaad50 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +# Copyright (C) The Arvados Authors. All rights reserved. +# +# SPDX-License-Identifier: AGPL-3.0 + +REACT_APP_ARVADOS_API_HOST=https://qr1hi.arvadosapi.com \ No newline at end of file diff --git a/README.md b/README.md index eaed1894..864a54fa 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,14 @@ yarn install yarn build +### Configuration +You can customize project global variables using env variables. Default values are placed in the `.env` file. + +Example: +``` +REACT_APP_ARVADOS_API_HOST=localhost:8000 yarn start +``` + ### Licensing Arvados is Free Software. See COPYING for information about Arvados Free diff --git a/src/common/api/common-resource-service.test.ts b/src/common/api/common-resource-service.test.ts index d28abc45..7093b59c 100644 --- a/src/common/api/common-resource-service.test.ts +++ b/src/common/api/common-resource-service.test.ts @@ -7,19 +7,36 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; describe("CommonResourceService", () => { - - const axiosMock = new MockAdapter(axios); + const axiosInstance = axios.create(); + const axiosMock = new MockAdapter(axiosInstance); beforeEach(() => { axiosMock.reset(); }); + it("#create", async () => { + axiosMock + .onPost("/resource/") + .reply(200, { owner_uuid: "ownerUuidValue" }); + + const commonResourceService = new CommonResourceService(axiosInstance, "resource"); + const resource = await commonResourceService.create({ ownerUuid: "ownerUuidValue" }); + expect(resource).toEqual({ ownerUuid: "ownerUuidValue" }); + }); + + it("#create maps request params to snake case", async () => { + axiosInstance.post = jest.fn(() => Promise.resolve({data: {}})); + const commonResourceService = new CommonResourceService(axiosInstance, "resource"); + await commonResourceService.create({ ownerUuid: "ownerUuidValue" }); + expect(axiosInstance.post).toHaveBeenCalledWith("/resource/", {owner_uuid: "ownerUuidValue"}); + }); + it("#delete", async () => { axiosMock .onDelete("/resource/uuid") .reply(200, { deleted_at: "now" }); - const commonResourceService = new CommonResourceService(axios, "resource"); + const commonResourceService = new CommonResourceService(axiosInstance, "resource"); const resource = await commonResourceService.delete("uuid"); expect(resource).toEqual({ deletedAt: "now" }); }); @@ -29,7 +46,7 @@ describe("CommonResourceService", () => { .onGet("/resource/uuid") .reply(200, { modified_at: "now" }); - const commonResourceService = new CommonResourceService(axios, "resource"); + const commonResourceService = new CommonResourceService(axiosInstance, "resource"); const resource = await commonResourceService.get("uuid"); expect(resource).toEqual({ modifiedAt: "now" }); }); @@ -47,7 +64,7 @@ describe("CommonResourceService", () => { items_available: 20 }); - const commonResourceService = new CommonResourceService(axios, "resource"); + const commonResourceService = new CommonResourceService(axiosInstance, "resource"); const resource = await commonResourceService.list({ limit: 10, offset: 1 }); expect(resource).toEqual({ kind: "kind", diff --git a/src/common/api/common-resource-service.ts b/src/common/api/common-resource-service.ts index fe6c752c..8df179cd 100644 --- a/src/common/api/common-resource-service.ts +++ b/src/common/api/common-resource-service.ts @@ -70,7 +70,7 @@ export default class CommonResourceService { create(data: Partial) { return this.serverApi - .post(this.resourceType, data) + .post(this.resourceType, CommonResourceService.mapKeys(_.snakeCase)(data)) .then(CommonResourceService.mapResponseKeys); } diff --git a/src/common/api/server-api.ts b/src/common/api/server-api.ts index 2e676dde..330ce657 100644 --- a/src/common/api/server-api.ts +++ b/src/common/api/server-api.ts @@ -4,7 +4,7 @@ import Axios, { AxiosInstance } from "axios"; -export const API_HOST = 'https://qr1hi.arvadosapi.com'; +export const API_HOST = process.env.REACT_APP_ARVADOS_API_HOST; export const serverApi: AxiosInstance = Axios.create({ baseURL: API_HOST + '/arvados/v1' diff --git a/src/components/context-menu/context-menu.tsx b/src/components/context-menu/context-menu.tsx index 7751be49..6ac1207b 100644 --- a/src/components/context-menu/context-menu.tsx +++ b/src/components/context-menu/context-menu.tsx @@ -27,7 +27,8 @@ export default class ContextMenu extends React.PureComponent + anchorOrigin={DefaultTransformOrigin} + onContextMenu={this.handleContextMenu}> {actions.map((group, groupIndex) => @@ -48,4 +49,9 @@ export default class ContextMenu extends React.PureComponent ; } + + handleContextMenu = (event: React.MouseEvent) => { + event.preventDefault(); + this.props.onClose(); + } } diff --git a/src/index.tsx b/src/index.tsx index 21ecdab1..be01e0a0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -23,22 +23,21 @@ const history = createBrowserHistory(); const store = configureStore(history); store.dispatch(authActions.INIT()); -const rootUuid = authService.getRootUuid(); -store.dispatch(getProjectList(rootUuid)); +store.dispatch(getProjectList(authService.getUuid())); const App = () =>
- - + +
; ReactDOM.render( - , + , document.getElementById('root') as HTMLElement ); diff --git a/src/models/group.ts b/src/models/group.ts index dae516bd..4bb9a7fd 100644 --- a/src/models/group.ts +++ b/src/models/group.ts @@ -8,11 +8,15 @@ import { ResourceKind } from "./kinds"; export interface GroupResource extends Resource { kind: ResourceKind.Group; name: string; - groupClass: string; + groupClass: GroupClass | null; description: string; properties: string; writeableBy: string[]; trashAt: string; deleteAt: string; isTrashed: boolean; +} + +export enum GroupClass { + Project = "project" } \ No newline at end of file diff --git a/src/models/project.ts b/src/models/project.ts index beb9810c..c44c8cc7 100644 --- a/src/models/project.ts +++ b/src/models/project.ts @@ -3,11 +3,11 @@ // SPDX-License-Identifier: AGPL-3.0 import { Resource as R } from "./resource"; -import { GroupResource } from "./group"; +import { GroupResource, GroupClass } from "./group"; export interface Project extends R { } export interface ProjectResource extends GroupResource { - groupClass: "project"; + groupClass: GroupClass.Project; } diff --git a/src/services/groups-service/groups-service.ts b/src/services/groups-service/groups-service.ts index ed61297d..2a5a51a8 100644 --- a/src/services/groups-service/groups-service.ts +++ b/src/services/groups-service/groups-service.ts @@ -12,7 +12,7 @@ import { CollectionResource } from "../../models/collection"; import { ProjectResource } from "../../models/project"; import { ProcessResource } from "../../models/process"; -interface ContensArguments { +export interface ContentsArguments { limit?: number; offset?: number; order?: OrderBuilder; @@ -25,13 +25,13 @@ export type GroupContentsResource = ProjectResource | ProcessResource; -export default class GroupsService extends CommonResourceService { +export default class GroupsService extends CommonResourceService { constructor(serverApi: AxiosInstance) { super(serverApi, "groups"); } - contents(uuid: string, args: ContensArguments = {}): Promise> { + contents(uuid: string, args: ContentsArguments = {}): Promise> { const { filters, order, ...other } = args; const params = { ...other, diff --git a/src/services/project-service/project-service.test.ts b/src/services/project-service/project-service.test.ts new file mode 100644 index 00000000..68df2450 --- /dev/null +++ b/src/services/project-service/project-service.test.ts @@ -0,0 +1,39 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import ProjectService from "./project-service"; +import FilterBuilder from "../../common/api/filter-builder"; +import { ProjectResource } from "../../models/project"; + +describe("CommonResourceService", () => { + const axiosInstance = axios.create(); + + it(`#create has groupClass set to "project"`, async () => { + axiosInstance.post = jest.fn(() => Promise.resolve({ data: {} })); + const projectService = new ProjectService(axiosInstance); + const resource = await projectService.create({ name: "nameValue" }); + expect(axiosInstance.post).toHaveBeenCalledWith("/groups/", { + name: "nameValue", + group_class: "project" + }); + }); + + + 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() + .addEqual("groupClass", "project") + .serialize() + } + }); + }); + +}); diff --git a/src/services/project-service/project-service.ts b/src/services/project-service/project-service.ts new file mode 100644 index 00000000..9ce9e213 --- /dev/null +++ b/src/services/project-service/project-service.ts @@ -0,0 +1,36 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// 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"; + +export default class ProjectService extends GroupsService { + + create(data: Partial) { + const projectData = { ...data, groupClass: GroupClass.Project }; + return super.create(projectData); + } + + list(args: ListArguments = {}) { + return super.list({ + ...args, + filters: this.addProjectFilter(args.filters) + }); + } + + private addProjectFilter(filters?: FilterBuilder) { + return FilterBuilder + .create() + .concat(filters + ? filters + : FilterBuilder.create()) + .concat(FilterBuilder + .create() + .addEqual("groupClass", GroupClass.Project)); + } + +} \ No newline at end of file diff --git a/src/services/services.ts b/src/services/services.ts index 51d2b760..1e9a74dd 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -6,7 +6,9 @@ import AuthService from "./auth-service/auth-service"; import CollectionService from "./collection-service/collection-service"; import GroupsService from "./groups-service/groups-service"; import { serverApi } from "../common/api/server-api"; +import ProjectService from "./project-service/project-service"; export const authService = new AuthService(); export const collectionService = new CollectionService(); export const groupsService = new GroupsService(serverApi); +export const projectService = new ProjectService(serverApi); diff --git a/src/store/project/project-action.ts b/src/store/project/project-action.ts index 4cc21979..51693496 100644 --- a/src/store/project/project-action.ts +++ b/src/store/project/project-action.ts @@ -3,10 +3,11 @@ // SPDX-License-Identifier: AGPL-3.0 import { default as unionize, ofType, UnionOf } from "unionize"; -import { Project } from "../../models/project"; -import { groupsService } from "../../services/services"; +import { Project, ProjectResource } from "../../models/project"; +import { projectService } from "../../services/services"; import { Dispatch } from "redux"; import { getResourceKind } from "../../models/resource"; +import FilterBuilder from "../../common/api/filter-builder"; const actions = unionize({ CREATE_PROJECT: ofType(), @@ -20,17 +21,21 @@ const actions = unionize({ tag: 'type', value: 'payload' }); - + export const getProjectList = (parentUuid: string = '') => (dispatch: Dispatch) => { - dispatch(actions.PROJECTS_REQUEST(parentUuid)); - return groupsService.list().then(listResults => { - const projects = listResults.items.map(item => ({ - ...item, - kind: getResourceKind(item.kind) - })); - dispatch(actions.PROJECTS_SUCCESS({ projects, parentItemId: parentUuid })); - return projects; - }); + dispatch(actions.PROJECTS_REQUEST(parentUuid)); + return projectService.list({ + filters: FilterBuilder + .create() + .addEqual("ownerUuid", parentUuid) + }).then(listResults => { + const projects = listResults.items.map(item => ({ + ...item, + kind: getResourceKind(item.kind) + })); + dispatch(actions.PROJECTS_SUCCESS({ projects, parentItemId: parentUuid })); + return projects; + }); }; export type ProjectAction = UnionOf; diff --git a/src/views/workbench/workbench.test.tsx b/src/views/workbench/workbench.test.tsx index 4b52d0b9..79a98ad6 100644 --- a/src/views/workbench/workbench.test.tsx +++ b/src/views/workbench/workbench.test.tsx @@ -16,16 +16,7 @@ const history = createBrowserHistory(); it('renders without crashing', () => { const div = document.createElement('div'); - const store = configureStore({ - projects: { - items: [], - currentItemId: "" - }, - collections: [], - router: { location: null }, - auth: {}, - sidePanel: [] - }, createBrowserHistory()); + const store = configureStore(createBrowserHistory()); ReactDOM.render(