import { Redirect, RouteProps } from "react-router";
import * as React from "react";
import { connect, DispatchProp } from "react-redux";
-import authActions, { getUserDetails } from "../../store/auth/auth-action";
+import authActions from "../../store/auth/auth-action";
+import { authService, projectService } from "../../services/services";
interface ApiTokenProps {
}
const search = this.props.location ? this.props.location.search : "";
const apiToken = ApiToken.getUrlParameter(search, 'api_token');
this.props.dispatch(authActions.SAVE_API_TOKEN(apiToken));
- this.props.dispatch(getUserDetails());
+ this.props.dispatch(authService.getUserDetails());
+ this.props.dispatch(projectService.getTopProjectList());
}
render() {
return <Redirect to="/"/>
import Workbench from './views/workbench/workbench';
import ProjectList from './components/project-list/project-list';
import './index.css';
-import { Redirect, Route, RouteProps, Router, RouterProps } from "react-router";
+import { Route } from "react-router";
import createBrowserHistory from "history/createBrowserHistory";
import configureStore from "./store/store";
import { ConnectedRouter } from "react-router-redux";
import ApiToken from "./components/api-token/api-token";
import authActions from "./store/auth/auth-action";
+import { projectService } from "./services/services";
const history = createBrowserHistory();
const store = configureStore({
projects: [
- { name: 'Mouse genome', createdAt: '2018-05-01' },
- { name: 'Human body', createdAt: '2018-05-01' },
- { name: 'Secret operation', createdAt: '2018-05-01' }
],
router: {
location: null
}, history);
store.dispatch(authActions.INIT());
+store.dispatch<any>(projectService.getTopProjectList());
const App = () =>
<Provider store={store}>
export interface Project {
name: string;
createdAt: string;
+ modifiedAt: string;
+ uuid: string;
+ ownerUuid: string;
+ href: string
}
//
// SPDX-License-Identifier: AGPL-3.0
-import { API_HOST } from "../../common/server-api";
+import { API_HOST, serverApi } from "../../common/server-api";
import { User } from "../../models/user";
+import { Dispatch } from "redux";
+import actions from "../../store/auth/auth-action";
export const API_TOKEN_KEY = 'apiToken';
export const USER_EMAIL_KEY = 'userEmail';
export const USER_FIRST_NAME_KEY = 'userFirstName';
export const USER_LAST_NAME_KEY = 'userLastName';
+export interface UserDetailsResponse {
+ email: string;
+ first_name: string;
+ last_name: string;
+ is_admin: boolean;
+}
+
export default class AuthService {
public saveApiToken(token: string) {
const currentUrl = `${window.location.protocol}//${window.location.host}`;
window.location.assign(`${API_HOST}/logout?return_to=${currentUrl}`);
}
+
+ public getUserDetails = () => (dispatch: Dispatch) => {
+ dispatch(actions.USER_DETAILS_REQUEST());
+ serverApi
+ .get<UserDetailsResponse>('/users/current')
+ .then(resp => {
+ dispatch(actions.USER_DETAILS_SUCCESS(resp.data));
+ })
+ // .catch(err => {
+ // });
+ };
}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { serverApi } from "../../common/server-api";
+import { Dispatch } from "redux";
+import actions from "../../store/project/project-action";
+import { Project } from "../../models/project";
+
+interface GroupsResponse {
+ offset: number;
+ limit: number;
+ items: Array<{
+ href: string;
+ kind: string;
+ etag: string;
+ uuid: string;
+ owner_uuid: string;
+ created_at: string;
+ modified_by_client_uuid: string;
+ modified_by_user_uuid: string;
+ modified_at: string;
+ name: string;
+ group_class: string;
+ description: string;
+ writable_by: string[];
+ delete_at: string;
+ trash_at: string;
+ is_trashed: boolean;
+ }>;
+}
+
+export default class ProjectService {
+ public getTopProjectList = () => (dispatch: Dispatch) => {
+ dispatch(actions.TOP_PROJECTS_REQUEST());
+ serverApi.get<GroupsResponse>('/groups').then(groups => {
+ const projects = groups.data.items.map(g => ({
+ name: g.name,
+ createdAt: g.created_at,
+ modifiedAt: g.modified_at,
+ href: g.href,
+ uuid: g.uuid,
+ ownerUuid: g.owner_uuid
+ } as Project));
+ dispatch(actions.TOP_PROJECTS_SUCCESS(projects));
+ });
+ }
+}
// SPDX-License-Identifier: AGPL-3.0
import AuthService from "./auth-service/auth-service";
+import ProjectService from "./project-service/project-service";
export const authService = new AuthService();
+export const projectService = new ProjectService();
//
// SPDX-License-Identifier: AGPL-3.0
-import { serverApi } from "../../common/server-api";
import { ofType, default as unionize, UnionOf } from "unionize";
-import { Dispatch } from "redux";
-
-export interface UserDetailsResponse {
- email: string;
- first_name: string;
- last_name: string;
- is_admin: boolean;
-}
+import { UserDetailsResponse } from "../../services/auth-service/auth-service";
const actions = unionize({
SAVE_API_TOKEN: ofType<string>(),
export type AuthAction = UnionOf<typeof actions>;
export default actions;
-
-export const getUserDetails = () => (dispatch: Dispatch) => {
- dispatch(actions.USER_DETAILS_REQUEST());
- serverApi
- .get<UserDetailsResponse>('/users/current')
- .then(resp => {
- dispatch(actions.USER_DETAILS_SUCCESS(resp.data));
- })
- // .catch(err => {
- // });
-};
-
-
//
// SPDX-License-Identifier: AGPL-3.0
-import actions, { AuthAction, UserDetailsResponse } from "./auth-action";
+import actions, { AuthAction } from "./auth-action";
import { User } from "../../models/user";
import { authService } from "../../services/services";
import { removeServerApiAuthorizationHeader, setServerApiAuthorizationHeader } from "../../common/server-api";
+import { UserDetailsResponse } from "../../services/auth-service/auth-service";
export interface AuthState {
user?: User;
INIT: () => {
const user = authService.getUser();
const token = authService.getApiToken();
+ if (token) {
+ setServerApiAuthorizationHeader(token);
+ }
return {user, apiToken: token};
},
LOGIN: () => {
const actions = unionize({
CREATE_PROJECT: ofType<Project>(),
- REMOVE_PROJECT: ofType<string>()
+ REMOVE_PROJECT: ofType<string>(),
+ TOP_PROJECTS_REQUEST: {},
+ TOP_PROJECTS_SUCCESS: ofType<Project[]>()
}, {
tag: 'type',
value: 'payload'
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import projectsReducer from "./project-reducer";
+import actions from "./project-action";
+
+describe('project-reducer', () => {
+ it('should add new project to the list', () => {
+ const initialState = undefined;
+ const project = {
+ name: 'test',
+ href: 'href',
+ createdAt: '2018-01-01',
+ modifiedAt: '2018-01-01',
+ ownerUuid: 'owner-test123',
+ uuid: 'test123'
+ };
+
+ const state = projectsReducer(initialState, actions.CREATE_PROJECT(project));
+ expect(state).toEqual([project]);
+ });
+
+ it('should load projects', () => {
+ const initialState = undefined;
+ const project = {
+ name: 'test',
+ href: 'href',
+ createdAt: '2018-01-01',
+ modifiedAt: '2018-01-01',
+ ownerUuid: 'owner-test123',
+ uuid: 'test123'
+ };
+
+ const topProjects = [project, project];
+ const state = projectsReducer(initialState, actions.TOP_PROJECTS_SUCCESS(topProjects));
+ expect(state).toEqual(topProjects);
+ });
+});
const projectsReducer = (state: ProjectState = [], action: ProjectAction) => {
return actions.match(action, {
- CREATE_PROJECT: (project) => [...state, project],
+ CREATE_PROJECT: project => [...state, project],
REMOVE_PROJECT: () => state,
+ TOP_PROJECTS_REQUEST: () => state,
+ TOP_PROJECTS_SUCCESS: projects => {
+ return projects;
+ },
default: () => state
});
};