Commit 5e79276b authored by Phil Hughes's avatar Phil Hughes

improve API calls by calling internal API to get data

render job items (needs improvements to components)
parent cfe4d2f2
...@@ -24,7 +24,7 @@ const Api = { ...@@ -24,7 +24,7 @@ const Api = {
branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch', branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch',
createBranchPath: '/api/:version/projects/:id/repository/branches', createBranchPath: '/api/:version/projects/:id/repository/branches',
pipelinesPath: '/api/:version/projects/:id/pipelines', pipelinesPath: '/api/:version/projects/:id/pipelines',
pipelineJobsPath: '/api/:version/projects/:id/pipelines/:pipeline_id/jobs', pipelineJobsPath: '/:project_path/pipelines/:id/builds.json',
group(groupId, callback) { group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId); const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
...@@ -232,8 +232,8 @@ const Api = { ...@@ -232,8 +232,8 @@ const Api = {
pipelineJobs(projectPath, pipelineId, params = {}) { pipelineJobs(projectPath, pipelineId, params = {}) {
const url = Api.buildUrl(this.pipelineJobsPath) const url = Api.buildUrl(this.pipelineJobsPath)
.replace(':id', encodeURIComponent(projectPath)) .replace(':project_path', projectPath)
.replace(':pipeline_id', pipelineId); .replace(':id', pipelineId);
return axios.get(url, { params }); return axios.get(url, { params });
}, },
......
<script> <script>
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import Icon from '../../../vue_shared/components/icon.vue';
import CiIcon from '../../../vue_shared/components/ci_icon.vue';
import Tabs from '../../../vue_shared/components/tabs/tabs'; import Tabs from '../../../vue_shared/components/tabs/tabs';
import Tab from '../../../vue_shared/components/tabs/tab.vue'; import Tab from '../../../vue_shared/components/tabs/tab.vue';
...@@ -7,15 +9,18 @@ export default { ...@@ -7,15 +9,18 @@ export default {
components: { components: {
Tabs, Tabs,
Tab, Tab,
Icon,
CiIcon,
}, },
computed: { computed: {
...mapGetters('pipelines', ['jobsCount', 'failedJobs']), ...mapGetters('pipelines', ['jobsCount', 'failedJobsCount']),
...mapState('pipelines', ['stages']),
}, },
mounted() { mounted() {
this.fetchJobs(); this.fetchStages();
}, },
methods: { methods: {
...mapActions('pipelines', ['fetchJobs']), ...mapActions('pipelines', ['fetchStages']),
}, },
}; };
</script> </script>
...@@ -27,11 +32,44 @@ export default { ...@@ -27,11 +32,44 @@ export default {
<template slot="title"> <template slot="title">
Jobs <span class="badge">{{ jobsCount }}</span> Jobs <span class="badge">{{ jobsCount }}</span>
</template> </template>
List all jobs here <div style="overflow: auto;">
<div
v-for="stage in stages"
:key="stage.id"
class="panel panel-default"
>
<div
class="panel-heading"
@click="() => stage.isCollapsed = !stage.isCollapsed"
>
<ci-icon :status="stage.status" />
{{ stage.title }}
<span class="badge">
{{ stage.jobs.length }}
</span>
<icon
:name="stage.isCollapsed ? 'angle-left' : 'angle-down'"
css-classes="pull-right"
/>
</div>
<div
class="panel-body"
v-show="!stage.isCollapsed"
>
<div
v-for="job in stage.jobs"
:key="job.id"
>
<ci-icon :status="job.status" />
{{ job.name }} #{{ job.id }}
</div>
</div>
</div>
</div>
</tab> </tab>
<tab> <tab>
<template slot="title"> <template slot="title">
Failed Jobs <span class="badge">{{ failedJobs.length }}</span> Failed Jobs <span class="badge">{{ failedJobsCount }}</span>
</template> </template>
List all failed jobs here List all failed jobs here
</tab> </tab>
......
import axios from 'axios';
import { __ } from '../../../../locale'; import { __ } from '../../../../locale';
import Api from '../../../../api'; import Api from '../../../../api';
import flash from '../../../../flash'; import flash from '../../../../flash';
...@@ -21,29 +22,40 @@ export const fetchLatestPipeline = ({ dispatch, rootState }, sha) => { ...@@ -21,29 +22,40 @@ export const fetchLatestPipeline = ({ dispatch, rootState }, sha) => {
.catch(() => dispatch('receiveLatestPipelineError')); .catch(() => dispatch('receiveLatestPipelineError'));
}; };
export const requestJobs = ({ commit }) => commit(types.REQUEST_JOBS); export const requestStages = ({ commit }) => commit(types.REQUEST_STAGES);
export const receiveJobsError = ({ commit }) => { export const receiveStagesError = ({ commit }) => {
flash(__('There was an error loading jobs')); flash(__('There was an error loading job stages'));
commit(types.RECEIVE_JOBS_ERROR); commit(types.RECEIVE_STAGES_ERROR);
}; };
export const receiveJobsSuccess = ({ commit }, data) => commit(types.RECEIVE_JOBS_SUCCESS, data); export const receiveStagesSuccess = ({ commit }, data) =>
commit(types.RECEIVE_STAGES_SUCCESS, data);
export const fetchStages = ({ dispatch, state, rootState }) => {
dispatch('requestStages');
export const fetchJobs = ({ dispatch, state, rootState }, page = '1') => { Api.pipelineJobs(rootState.currentProjectId, state.latestPipeline.id)
dispatch('requestJobs'); .then(({ data }) => dispatch('receiveStagesSuccess', data))
.then(() => state.stages.forEach(stage => dispatch('fetchJobs', stage)))
.catch(() => dispatch('receiveStagesError'));
};
Api.pipelineJobs(rootState.currentProjectId, state.latestPipeline.id, { export const requestJobs = ({ commit }, id) => commit(types.REQUEST_JOBS, id);
page, export const receiveJobsError = ({ commit }, id) => {
}) flash(__('There was an error loading jobs'));
.then(({ data, headers }) => { commit(types.RECEIVE_JOBS_ERROR, id);
const nextPage = headers && headers['x-next-page']; };
export const receiveJobsSuccess = ({ commit }, { id, data }) =>
commit(types.RECEIVE_JOBS_SUCCESS, { id, data });
dispatch('receiveJobsSuccess', data); export const fetchJobs = ({ dispatch }, stage) => {
dispatch('requestJobs', stage.id);
if (nextPage) { axios
dispatch('fetchJobs', nextPage); .get(stage.dropdown_path)
} .then(({ data }) => {
dispatch('receiveJobsSuccess', { id: stage.id, data });
}) })
.catch(() => dispatch('receiveJobsError')); .catch(() => dispatch('receiveJobsError', stage.id));
}; };
export default () => {}; export default () => {};
export const hasLatestPipeline = state => !state.isLoadingPipeline && !!state.latestPipeline; export const hasLatestPipeline = state => !state.isLoadingPipeline && !!state.latestPipeline;
export const failedJobs = state => export const failedJobsCount = state =>
state.stages.reduce( state.stages.reduce(
(acc, stage) => acc.concat(stage.jobs.filter(job => job.status === 'failed')), (acc, stage) => acc + stage.jobs.filter(j => j.status.label === 'failed').length,
[], 0,
); );
export const jobsCount = state => state.stages.reduce((acc, stage) => acc + stage.jobs.length, 0); export const jobsCount = state => state.stages.reduce((acc, stage) => acc + stage.jobs.length, 0);
...@@ -2,6 +2,10 @@ export const REQUEST_LATEST_PIPELINE = 'REQUEST_LATEST_PIPELINE'; ...@@ -2,6 +2,10 @@ export const REQUEST_LATEST_PIPELINE = 'REQUEST_LATEST_PIPELINE';
export const RECEIVE_LASTEST_PIPELINE_ERROR = 'RECEIVE_LASTEST_PIPELINE_ERROR'; export const RECEIVE_LASTEST_PIPELINE_ERROR = 'RECEIVE_LASTEST_PIPELINE_ERROR';
export const RECEIVE_LASTEST_PIPELINE_SUCCESS = 'RECEIVE_LASTEST_PIPELINE_SUCCESS'; export const RECEIVE_LASTEST_PIPELINE_SUCCESS = 'RECEIVE_LASTEST_PIPELINE_SUCCESS';
export const REQUEST_STAGES = 'REQUEST_STAGES';
export const RECEIVE_STAGES_ERROR = 'RECEIVE_STAGES_ERROR';
export const RECEIVE_STAGES_SUCCESS = 'RECEIVE_STAGES_SUCCESS';
export const REQUEST_JOBS = 'REQUEST_JOBS'; export const REQUEST_JOBS = 'REQUEST_JOBS';
export const RECEIVE_JOBS_ERROR = 'RECEIVE_JOBS_ERROR'; export const RECEIVE_JOBS_ERROR = 'RECEIVE_JOBS_ERROR';
export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS'; export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS';
...@@ -18,37 +18,52 @@ export default { ...@@ -18,37 +18,52 @@ export default {
}; };
} }
}, },
[types.REQUEST_JOBS](state) { [types.REQUEST_STAGES](state) {
state.isLoadingJobs = true; state.isLoadingJobs = true;
}, },
[types.RECEIVE_JOBS_ERROR](state) { [types.RECEIVE_STAGES_ERROR](state) {
state.isLoadingJobs = false; state.isLoadingJobs = false;
}, },
[types.RECEIVE_JOBS_SUCCESS](state, jobs) { [types.RECEIVE_STAGES_SUCCESS](state, stages) {
state.isLoadingJobs = false; state.isLoadingJobs = false;
state.stages = jobs.reduce((acc, job) => { state.stages = stages.map((stage, i) => ({
let stage = acc.find(s => s.title === job.stage); ...stage,
id: i,
if (!stage) { isCollapsed: false,
stage = { isLoading: false,
title: job.stage, jobs: [],
isCollapsed: false, }));
jobs: [], },
}; [types.REQUEST_JOBS](state, id) {
state.stages = state.stages.reduce(
acc.push(stage); (acc, stage) =>
} acc.concat({
...stage,
stage.jobs = stage.jobs.concat({ isLoading: id === stage.id ? true : stage.isLoading,
id: job.id, }),
name: job.name, [],
status: job.status, );
stage: job.stage, },
duration: job.duration, [types.RECEIVE_JOBS_ERROR](state, id) {
}); state.stages = state.stages.reduce(
(acc, stage) =>
return acc; acc.concat({
}, state.stages); ...stage,
isLoading: id === stage.id ? false : stage.isLoading,
}),
[],
);
},
[types.RECEIVE_JOBS_SUCCESS](state, { id, data }) {
state.stages = state.stages.reduce(
(acc, stage) =>
acc.concat({
...stage,
isLoading: id === stage.id ? false : stage.isLoading,
jobs: id === stage.id ? data.latest_statuses : stage.jobs,
}),
[],
);
}, },
}; };
...@@ -26,6 +26,9 @@ export default { ...@@ -26,6 +26,9 @@ export default {
created() { created() {
this.isTab = true; this.isTab = true;
}, },
updated() {
this.$parent.$forceUpdate();
},
}; };
</script> </script>
......
...@@ -76,7 +76,16 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -76,7 +76,16 @@ class Projects::PipelinesController < Projects::ApplicationController
end end
def builds def builds
render_show respond_to do |format|
format.html do
render_show
end
format.json do
render json: PipelineSerializer
.new(project: @project, current_user: @current_user)
.represent_stages(@pipeline)
end
end
end end
def failures def failures
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment