Commit 50f703f9 authored by Steve Azzopardi's avatar Steve Azzopardi Committed by Kamil Trzciński

Move job stuck status to backend

parent 5726e51a
......@@ -77,11 +77,11 @@
'shouldRenderCalloutMessage',
'shouldRenderTriggeredLabel',
'hasEnvironment',
'isJobStuck',
'hasTrace',
'emptyStateIllustration',
'isScrollingDown',
'emptyStateAction',
'hasRunnersForProject',
]),
shouldRenderContent() {
......@@ -195,9 +195,9 @@
<!-- Body Section -->
<stuck-block
v-if="isJobStuck"
v-if="job.stuck"
class="js-job-stuck"
:has-no-runners-for-project="job.runners.available"
:has-no-runners-for-project="hasRunnersForProject"
:tags="job.tags"
:runners-path="runnerSettingsUrl"
/>
......
......@@ -23,14 +23,7 @@ export default {
<template>
<div class="bs-callout bs-callout-warning">
<p
v-if="hasNoRunnersForProject"
class="js-stuck-no-runners append-bottom-0"
>
{{ s__(`Job|This job is stuck, because the project
doesn't have any runners online assigned to it.`) }}
</p>
<p
v-else-if="tags.length"
v-if="tags.length"
class="js-stuck-with-tags append-bottom-0"
>
{{ s__(`This job is stuck, because you don't have
......@@ -43,6 +36,13 @@ export default {
{{ tag }}
</span>
</p>
<p
v-else-if="hasNoRunnersForProject"
class="js-stuck-no-runners append-bottom-0"
>
{{ s__(`Job|This job is stuck, because the project
doesn't have any runners online assigned to it.`) }}
</p>
<p
v-else
class="js-stuck-no-active-runner append-bottom-0"
......
......@@ -41,17 +41,10 @@ export const emptyStateIllustration = state =>
(state.job && state.job.status && state.job.status.illustration) || {};
export const emptyStateAction = state => (state.job && state.job.status && state.job.status.action) || {};
/**
* When the job is pending and there are no available runners
* we need to render the stuck block;
*
* @returns {Boolean}
*/
export const isJobStuck = state =>
(!_.isEmpty(state.job.status) && state.job.status.group === 'pending') &&
(!_.isEmpty(state.job.runners) && state.job.runners.available === false);
export const isScrollingDown = state => isScrolledToBottom() && !state.isTraceComplete;
export const hasRunnersForProject = state => state.job.runners.available && !state.job.runners.online;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -5,6 +5,7 @@ class BuildDetailsEntity < JobEntity
expose :tag_list, as: :tags
expose :has_trace?, as: :has_trace
expose :stage
expose :stuck?, as: :stuck
expose :user, using: UserEntity
expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity
......
---
title: Renders stuck block when runners are stuck
merge_request:
author:
type: fixed
......@@ -297,6 +297,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response).to match_response_schema('job/job_details')
expect(json_response['runners']['online']).to be false
expect(json_response['runners']['available']).to be false
expect(json_response['stuck']).to be true
end
end
......@@ -309,6 +310,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response).to match_response_schema('job/job_details')
expect(json_response['runners']['online']).to be false
expect(json_response['runners']['available']).to be true
expect(json_response['stuck']).to be true
end
end
......
......@@ -721,6 +721,62 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
expect(page).not_to have_css('.js-job-sidebar.right-sidebar-collpased')
end
end
context 'stuck', :js do
before do
visit project_job_path(project, job)
wait_for_requests
end
context 'without active runners available' do
let(:runner) { create(:ci_runner, :instance, active: false) }
let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner: runner) }
it 'renders message about job being stuck because no runners are active' do
expect(page).to have_css('.js-stuck-no-active-runner')
expect(page).to have_content("This job is stuck, because you don't have any active runners that can run this job.")
end
end
context 'when available runners can not run specified tag' do
let(:runner) { create(:ci_runner, :instance, active: false) }
let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner: runner, tag_list: %w(docker linux)) }
it 'renders message about job being stuck because of no runners with the specified tags' do
expect(page).to have_css('.js-stuck-with-tags')
expect(page).to have_content("This job is stuck, because you don't have any active runners online with any of these tags assigned to them:")
end
end
context 'when runners are offline and build has tags' do
let(:runner) { create(:ci_runner, :instance, active: true) }
let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner: runner, tag_list: %w(docker linux)) }
it 'renders message about job being stuck because of no runners with the specified tags' do
expect(page).to have_css('.js-stuck-with-tags')
expect(page).to have_content("This job is stuck, because you don't have any active runners online with any of these tags assigned to them:")
end
end
context 'without any runners available' do
let(:job) { create(:ci_build, :pending, pipeline: pipeline) }
it 'renders message about job being stuck because not runners are available' do
expect(page).to have_css('.js-stuck-no-active-runner')
expect(page).to have_content("This job is stuck, because you don't have any active runners that can run this job.")
end
end
context 'without available runners online' do
let(:runner) { create(:ci_runner, :instance, active: true) }
let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner: runner) }
it 'renders message about job being stuck because runners are offline' do
expect(page).to have_css('.js-stuck-no-runners')
expect(page).to have_content("This job is stuck, because the project doesn't have any runners online assigned to it.")
end
end
end
end
describe "POST /:project/jobs/:id/cancel", :js do
......
......@@ -18,6 +18,7 @@
"runner": { "$ref": "runner.json" },
"runners": { "$ref": "runners.json" },
"has_trace": { "type": "boolean" },
"stage": { "type": "string" }
"stage": { "type": "string" },
"stuck": { "type": "boolean" }
}
}
......@@ -88,7 +88,9 @@ describe('Job App ', () => {
describe('triggered job', () => {
beforeEach(() => {
mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { started: '2017-05-24T10:59:52.000+01:00' }));
mock
.onGet(props.endpoint)
.replyOnce(200, Object.assign({}, job, { started: '2017-05-24T10:59:52.000+01:00' }));
vm = mountComponentWithStore(Component, { props, store });
});
......@@ -133,57 +135,106 @@ describe('Job App ', () => {
});
describe('stuck block', () => {
it('renders stuck block when there are no runners', done => {
mock.onGet(props.endpoint).replyOnce(
200,
Object.assign({}, job, {
status: {
group: 'pending',
icon: 'status_pending',
label: 'pending',
text: 'pending',
details_path: 'path',
},
runners: {
available: false,
},
}),
);
vm = mountComponentWithStore(Component, { props, store });
setTimeout(() => {
expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
describe('without active runners availabl', () => {
it('renders stuck block when there are no runners', done => {
mock.onGet(props.endpoint).replyOnce(
200,
Object.assign({}, job, {
status: {
group: 'pending',
icon: 'status_pending',
label: 'pending',
text: 'pending',
details_path: 'path',
},
stuck: true,
runners: {
available: false,
online: false,
},
tags: [],
}),
);
vm = mountComponentWithStore(Component, { props, store });
done();
}, 0);
setTimeout(() => {
expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(
"This job is stuck, because you don't have any active runners that can run this job.",
);
done();
}, 0);
});
});
it('renders tags in stuck block when there are no runners', done => {
mock.onGet(props.endpoint).replyOnce(
200,
Object.assign({}, job, {
status: {
group: 'pending',
icon: 'status_pending',
label: 'pending',
text: 'pending',
details_path: 'path',
},
runners: {
available: false,
},
}),
);
describe('when available runners can not run specified tag', () => {
it('renders tags in stuck block when there are no runners', done => {
mock.onGet(props.endpoint).replyOnce(
200,
Object.assign({}, job, {
status: {
group: 'pending',
icon: 'status_pending',
label: 'pending',
text: 'pending',
details_path: 'path',
},
stuck: true,
runners: {
available: false,
online: false,
},
}),
);
vm = mountComponentWithStore(Component, {
props,
store,
vm = mountComponentWithStore(Component, {
props,
store,
});
setTimeout(() => {
expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(
"This job is stuck, because you don't have any active runners online with any of these tags assigned to them:",
);
done();
}, 0);
});
});
setTimeout(() => {
expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
done();
}, 0);
describe('when runners are offline and build has tags', () => {
it('renders message about job being stuck because of no runners with the specified tags', done => {
mock.onGet(props.endpoint).replyOnce(
200,
Object.assign({}, job, {
status: {
group: 'pending',
icon: 'status_pending',
label: 'pending',
text: 'pending',
details_path: 'path',
},
stuck: true,
runners: {
available: true,
online: true,
},
}),
);
vm = mountComponentWithStore(Component, {
props,
store,
});
setTimeout(() => {
expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(
"This job is stuck, because you don't have any active runners online with any of these tags assigned to them:",
);
done();
}, 0);
})
});
it('does not renders stuck block when there are no runners', done => {
......@@ -418,10 +469,11 @@ describe('Job App ', () => {
vm.$store.state.trace = 'Update';
setTimeout(() => {
expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain('Update');
expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain(
'Different',
expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain(
'Update',
);
expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Different');
done();
}, 0);
});
......
......@@ -175,43 +175,37 @@ describe('Job Store Getters', () => {
});
});
describe('isJobStuck', () => {
describe('when job is pending and runners are not available', () => {
describe('hasRunnersForProject', () => {
describe('with available and offline runners', () => {
it('returns true', () => {
localState.job.status = {
group: 'pending',
};
localState.job.runners = {
available: false,
available: true,
online: false
};
expect(getters.isJobStuck(localState)).toEqual(true);
expect(getters.hasRunnersForProject(localState)).toEqual(true);
});
});
describe('when job is not pending', () => {
describe('with non available runners', () => {
it('returns false', () => {
localState.job.status = {
group: 'running',
};
localState.job.runners = {
available: false,
online: false
};
expect(getters.isJobStuck(localState)).toEqual(false);
expect(getters.hasRunnersForProject(localState)).toEqual(false);
});
});
describe('when runners are available', () => {
describe('with online runners', () => {
it('returns false', () => {
localState.job.status = {
group: 'pending',
};
localState.job.runners = {
available: true,
available: false,
online: true
};
expect(getters.isJobStuck(localState)).toEqual(false);
expect(getters.hasRunnersForProject(localState)).toEqual(false);
});
});
});
......
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