Skip to content

Commit

Permalink
Connect Tasks container to redux store (tektoncd#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlanGreene authored Apr 23, 2019
1 parent e098866 commit 8a0d432
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 27 deletions.
35 changes: 35 additions & 0 deletions src/actions/tasks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Copyright 2019 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { getTasks } from '../api';

export function fetchTasksSuccess(data) {
return {
type: 'TASKS_FETCH_SUCCESS',
data
};
}

export function fetchTasks() {
return async dispatch => {
dispatch({ type: 'TASKS_FETCH_REQUEST' });
let tasks;
try {
tasks = await getTasks();
dispatch(fetchTasksSuccess(tasks));
} catch (error) {
dispatch({ type: 'TASKS_FETCH_FAILURE', error });
}
return tasks;
};
}
63 changes: 63 additions & 0 deletions src/actions/tasks.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright 2019 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';

import * as API from '../api';
import { fetchTasks, fetchTasksSuccess } from './tasks';

it('fetchTasksSuccess', () => {
const data = { fake: 'data' };
expect(fetchTasksSuccess(data)).toEqual({
type: 'TASKS_FETCH_SUCCESS',
data
});
});

it('fetchTasks', async () => {
const tasks = { fake: 'tasks' };
const middleware = [thunk];
const mockStore = configureStore(middleware);
const store = mockStore();

jest.spyOn(API, 'getTasks').mockImplementation(() => tasks);

const expectedActions = [
{ type: 'TASKS_FETCH_REQUEST' },
fetchTasksSuccess(tasks)
];

await store.dispatch(fetchTasks());
expect(store.getActions()).toEqual(expectedActions);
});

it('fetchTasks error', async () => {
const middleware = [thunk];
const mockStore = configureStore(middleware);
const store = mockStore();

const error = new Error();

jest.spyOn(API, 'getTasks').mockImplementation(() => {
throw error;
});

const expectedActions = [
{ type: 'TASKS_FETCH_REQUEST' },
{ type: 'TASKS_FETCH_FAILURE', error }
];

await store.dispatch(fetchTasks());
expect(store.getActions()).toEqual(expectedActions);
});
4 changes: 2 additions & 2 deletions src/containers/Pipelines/Pipelines.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ import {

import '../../components/Definitions/Definitions.scss';

/* istanbul ignore next */
export class Pipelines extends Component {
export /* istanbul ignore next */ class Pipelines extends Component {
componentDidMount() {
this.props.fetchPipelines();
}
Expand Down Expand Up @@ -110,6 +109,7 @@ Pipelines.defaultProps = {
pipelines: []
};

/* istanbul ignore next */
function mapStateToProps(state) {
return {
error: getPipelinesErrorMessage(state),
Expand Down
60 changes: 38 additions & 22 deletions src/containers/Tasks/Tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ limitations under the License.
*/

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { Link, NavLink } from 'react-router-dom';
import { connect } from 'react-redux';
import {
Breadcrumb,
BreadcrumbItem,
Expand All @@ -26,42 +27,37 @@ import {
} from 'carbon-components-react';

import Header from '../../components/Header';
import { getTasks } from '../../api';
import { fetchTasks } from '../../actions/tasks';
import {
getTasks,
getTasksErrorMessage,
isFetchingTasks
} from '../../reducers';

import '../../components/Definitions/Definitions.scss';

/* istanbul ignore next */
class Tasks extends Component {
state = {
error: null,
loading: true,
tasks: []
};

async componentDidMount() {
try {
const tasks = await getTasks();
this.setState({ tasks, loading: false });
} catch (error) {
this.setState({ error, loading: false });
}
export /* istanbul ignore next */ class Tasks extends Component {
componentDidMount() {
this.props.fetchTasks();
}

render() {
const { error, loading, tasks } = this.state;
const { error, loading, tasks } = this.props;

return (
<div className="definitions">
<Header>
<div className="definitions-header">
<Breadcrumb>
<BreadcrumbItem href="#">Tasks</BreadcrumbItem>
<BreadcrumbItem>
<NavLink to="/tasks">Tasks</NavLink>
</BreadcrumbItem>
</Breadcrumb>
</div>
</Header>
<main>
{(() => {
if (loading) {
if (loading && !tasks.length) {
return <StructuredListSkeleton border />;
}

Expand All @@ -70,7 +66,7 @@ class Tasks extends Component {
<InlineNotification
kind="error"
title="Error loading tasks"
subtitle={JSON.stringify(error)}
subtitle={error}
/>
);
}
Expand Down Expand Up @@ -106,4 +102,24 @@ class Tasks extends Component {
}
}

export default Tasks;
Tasks.defaultProps = {
tasks: []
};

/* istanbul ignore next */
function mapStateToProps(state) {
return {
error: getTasksErrorMessage(state),
loading: isFetchingTasks(state),
tasks: getTasks(state)
};
}

const mapDispatchToProps = {
fetchTasks
};

export default connect(
mapStateToProps,
mapDispatchToProps
)(Tasks);
16 changes: 15 additions & 1 deletion src/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ limitations under the License.
import { combineReducers } from 'redux';

import pipelines, * as pipelineSelectors from './pipelines';
import tasks, * as taskSelectors from './tasks';

export default combineReducers({
pipelines
pipelines,
tasks
});

export function getPipelines(state) {
Expand All @@ -30,3 +32,15 @@ export function getPipelinesErrorMessage(state) {
export function isFetchingPipelines(state) {
return pipelineSelectors.isFetchingPipelines(state.pipelines);
}

export function getTasks(state) {
return taskSelectors.getTasks(state.tasks);
}

export function getTasksErrorMessage(state) {
return taskSelectors.getTasksErrorMessage(state.tasks);
}

export function isFetchingTasks(state) {
return taskSelectors.isFetchingTasks(state.tasks);
}
34 changes: 32 additions & 2 deletions src/reducers/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { getPipelines, getPipelinesErrorMessage, isFetchingPipelines } from '.';
import {
getPipelines,
getPipelinesErrorMessage,
getTasks,
getTasksErrorMessage,
isFetchingPipelines,
isFetchingTasks
} from '.';
import * as pipelineSelectors from './pipelines';
import * as taskSelectors from './tasks';

const pipelines = { fake: 'pipelines' };
const state = { pipelines };
const tasks = { fake: 'tasks' };
const state = { pipelines, tasks };

it('getPipelines', () => {
jest
Expand Down Expand Up @@ -45,3 +54,24 @@ it('isFetchingPipelines', () => {
state.pipelines
);
});

it('getTasks', () => {
jest.spyOn(taskSelectors, 'getTasks').mockImplementation(() => tasks);
expect(getTasks(state)).toEqual(tasks);
expect(taskSelectors.getTasks).toHaveBeenCalledWith(state.tasks);
});

it('getTasksErrorMessage', () => {
const errorMessage = 'fake error message';
jest
.spyOn(taskSelectors, 'getTasksErrorMessage')
.mockImplementation(() => errorMessage);
expect(getTasksErrorMessage(state)).toEqual(errorMessage);
expect(taskSelectors.getTasksErrorMessage).toHaveBeenCalledWith(state.tasks);
});

it('isFetchingTasks', () => {
jest.spyOn(taskSelectors, 'isFetchingTasks').mockImplementation(() => true);
expect(isFetchingTasks(state)).toBe(true);
expect(taskSelectors.isFetchingTasks).toHaveBeenCalledWith(state.tasks);
});
70 changes: 70 additions & 0 deletions src/reducers/tasks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
Copyright 2019 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { combineReducers } from 'redux';
import keyBy from 'lodash.keyby';

function byName(state = {}, action) {
switch (action.type) {
case 'TASKS_FETCH_SUCCESS':
return keyBy(action.data, 'metadata.name');
default:
return state;
}
}

function isFetching(state = false, action) {
switch (action.type) {
case 'TASKS_FETCH_REQUEST':
return true;
case 'TASKS_FETCH_SUCCESS':
case 'TASKS_FETCH_FAILURE':
return false;
default:
return state;
}
}

function errorMessage(state = null, action) {
switch (action.type) {
case 'TASKS_FETCH_FAILURE':
return action.error.message;
case 'TASKS_FETCH_REQUEST':
case 'TASKS_FETCH_SUCCESS':
return null;
default:
return state;
}
}

export default combineReducers({
byName,
errorMessage,
isFetching
});

export function getTasks(state) {
return Object.values(state.byName);
}

export function getTask(state, name) {
return state.byName[name];
}

export function getTasksErrorMessage(state) {
return state.errorMessage;
}

export function isFetchingTasks(state) {
return state.isFetching;
}
Loading

0 comments on commit 8a0d432

Please sign in to comment.