Skip to content

Commit

Permalink
expose taskRuns at top level navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
steveodonovan authored and tekton-robot committed Jun 18, 2019
1 parent d756c88 commit 8106961
Show file tree
Hide file tree
Showing 12 changed files with 495 additions and 5 deletions.
17 changes: 16 additions & 1 deletion src/actions/taskRuns.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { getTaskRuns } from '../api';
import { getTaskRun, getTaskRuns } from '../api';
import { getSelectedNamespace } from '../reducers';

export function fetchTaskRunsSuccess(data) {
Expand All @@ -35,3 +35,18 @@ export function fetchTaskRuns({ taskName, namespace } = {}) {
return taskRuns;
};
}

export function fetchTaskRun({ taskRunName, namespace } = {}) {
return async (dispatch, getState) => {
dispatch({ type: 'TASK_RUNS_FETCH_REQUEST' });
let taskRun;
try {
const requestedNamespace = namespace || getSelectedNamespace(getState());
taskRun = await getTaskRun(taskRunName, requestedNamespace);
dispatch(fetchTaskRunsSuccess([taskRun]));
} catch (error) {
dispatch({ type: 'TASK_RUNS_FETCH_FAILURE', error });
}
return taskRun;
};
}
23 changes: 22 additions & 1 deletion src/actions/taskRuns.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import thunk from 'redux-thunk';

import * as API from '../api';
import * as selectors from '../reducers';
import { fetchTaskRuns, fetchTaskRunsSuccess } from './taskRuns';
import { fetchTaskRun, fetchTaskRuns, fetchTaskRunsSuccess } from './taskRuns';

it('fetchTaskRunsSuccess', () => {
const data = { fake: 'data' };
Expand Down Expand Up @@ -47,6 +47,27 @@ it('fetchTaskRuns', async () => {
expect(store.getActions()).toEqual(expectedActions);
});

it('fetchTaskRun', async () => {
const namespace = 'default';
const task = { fake: 'task' };
const middleware = [thunk];
const mockStore = configureStore(middleware);
const store = mockStore();

jest
.spyOn(selectors, 'getSelectedNamespace')
.mockImplementation(() => namespace);
jest.spyOn(API, 'getTaskRun').mockImplementation(() => task);

const expectedActions = [
{ type: 'TASK_RUNS_FETCH_REQUEST' },
fetchTaskRunsSuccess([task])
];

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

it('fetchTaskRuns error', async () => {
const middleware = [thunk];
const mockStore = configureStore(middleware);
Expand Down
6 changes: 5 additions & 1 deletion src/components/App/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,8 @@ main {

.bx--modal-content {
margin-bottom: $carbon--spacing-07
}
}

.bx--structured-list-td {
word-break: break-word;
}
13 changes: 13 additions & 0 deletions src/containers/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import {
PipelineRuns,
Pipelines,
SideNav,
TaskRun,
TaskRunList,
TaskRuns,
Tasks
} from '..';
Expand Down Expand Up @@ -88,11 +90,22 @@ export /* istanbul ignore next */ class App extends Component {
path="/namespaces/:namespace/pipelineruns"
component={PipelineRuns}
/>
<Route
path="/namespaces/:namespace/taskruns"
exact
component={TaskRunList}
/>
<Route
path="/namespaces/:namespace/taskruns/:taskRunName"
exact
component={TaskRun}
/>
<Route
path="/namespaces/:namespace/pipelines/:pipelineName/runs"
exact
component={PipelineRuns}
/>
<Route path="/taskruns" component={TaskRunList} />
<Route
path="/namespaces/:namespace/tasks/:taskName/runs"
exact
Expand Down
7 changes: 7 additions & 0 deletions src/containers/SideNav/SideNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ export class SideNav extends Component {
>
Tasks
</SideNavLink>
<SideNavLink
element={NavLink}
icon={<span />}
to={this.getPath('/taskruns')}
>
TaskRuns
</SideNavLink>
<SideNavLink
element={NavLink}
icon={<span />}
Expand Down
216 changes: 216 additions & 0 deletions src/containers/TaskRun/TaskRun.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
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 React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {
InlineNotification,
StructuredListSkeleton
} from 'carbon-components-react';

import {
getSelectedNamespace,
getTask,
getTaskRun,
getTaskRunsErrorMessage
} from '../../reducers';

import RunHeader from '../../components/RunHeader';
import StepDetails from '../../components/StepDetails';
import TaskTree from '../../components/TaskTree';
import { getStatus, stepsStatus, taskRunStep } from '../../utils';

import '../../components/Run/Run.scss';
import { fetchTask } from '../../actions/tasks';
import { fetchTaskRun } from '../../actions/taskRuns';

export /* istanbul ignore next */ class TaskRunContainer extends Component {
// once redux store is available errors will be handled properly with dedicated components
static notification(notification) {
const { kind, message } = notification;
const titles = {
info: 'Task run not available',
error: 'Error loading task run'
};
return (
<InlineNotification
kind={kind}
title={titles[kind]}
subtitle={JSON.stringify(message, Object.getOwnPropertyNames(message))}
/>
);
}

state = {
loading: true,
selectedStepId: null
};

componentDidMount() {
const { match, namespace } = this.props;
const { taskRunName } = match.params;
this.fetchTaskAndRuns(taskRunName, namespace);
}

componentDidUpdate(prevProps) {
const { match, namespace } = this.props;
const { taskRunName } = match.params;
const { match: prevMatch, namespace: prevNamespace } = prevProps;
const { taskRunName: prevTaskRunName } = prevMatch.params;

if (taskRunName !== prevTaskRunName || namespace !== prevNamespace) {
this.setState({ loading: true }); // eslint-disable-line
this.fetchTaskAndRuns(taskRunName, namespace);
}
}

handleTaskSelected = (_, selectedStepId) => {
this.setState({ selectedStepId });
};

loadTaskRun = () => {
const { task } = this.props;
let { taskRun } = this.props;
const taskRunName = taskRun.metadata.name;
const { reason, status: succeeded } = getStatus(taskRun);
const runSteps = stepsStatus(
task ? task.spec.steps : taskRun.status.steps,
taskRun.status.steps
);
const { startTime } = taskRun.status;
taskRun = {
id: taskRun.metadata.uid,
pod: taskRun.status.podName,
pipelineTaskName: taskRunName,
reason,
steps: runSteps,
succeeded,
taskRunName,
startTime
};
return taskRun;
};

fetchTaskAndRuns(taskRunName, namespace) {
this.props.fetchTaskRun({ taskRunName, namespace }).then(taskRun => {
if (taskRun && taskRun.spec.taskRef) {
this.props
.fetchTask({ name: taskRun.spec.taskRef.name, namespace })
.then(() => {
this.setState({ loading: false });
});
} else {
this.setState({ loading: false });
}
});
}

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

if (loading) {
return <StructuredListSkeleton border />;
}

if (error) {
return TaskRunContainer.notification({
kind: 'error',
message: 'Error loading task run'
});
}

const taskRun = this.loadTaskRun();

if (!taskRun) {
return TaskRunContainer.notification({
kind: 'info',
message: 'Task run not available'
});
}

const { definition, reason, status, stepName, stepStatus } = taskRunStep(
selectedStepId,
taskRun
);

return (
<>
<RunHeader
lastTransitionTime={taskRun.startTime}
loading={loading}
runName={taskRun.taskRunName}
status={taskRun.succeeded}
/>
<div className="tasks">
<TaskTree
onSelect={this.handleTaskSelected}
selectedTaskId={taskRun.id}
taskRuns={[taskRun]}
/>
{selectedStepId && (
<StepDetails
definition={definition}
reason={reason}
status={status}
stepName={stepName}
stepStatus={stepStatus}
taskRun={taskRun}
/>
)}
</div>
</>
);
}
}

TaskRunContainer.propTypes = {
match: PropTypes.shape({
params: PropTypes.shape({
taskRunName: PropTypes.string.isRequired
}).isRequired
}).isRequired
};

/* istanbul ignore next */
function mapStateToProps(state, ownProps) {
const { match } = ownProps;
const { namespace: namespaceParam, taskRunName } = match.params;

const namespace = namespaceParam || getSelectedNamespace(state);
const taskRun = getTaskRun(state, {
name: taskRunName,
namespace
});
let task;
if (taskRun && taskRun.spec.taskRef) {
task = getTask(state, { name: taskRun.spec.taskRef.name, namespace });
}
return {
error: getTaskRunsErrorMessage(state),
namespace,
taskRun,
task
};
}

const mapDispatchToProps = {
fetchTask,
fetchTaskRun
};

export default connect(
mapStateToProps,
mapDispatchToProps
)(TaskRunContainer);
14 changes: 14 additions & 0 deletions src/containers/TaskRun/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
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.
*/

export { default } from './TaskRun';
Loading

0 comments on commit 8106961

Please sign in to comment.