Commit a5412de5 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch '52202-consider-moving-isjobstuck-verification-to-backend' into 'master'

Move job stuck status to backend

Closes #52202

See merge request gitlab-org/gitlab-ce!22442
parents 5726e51a 50f703f9
...@@ -77,11 +77,11 @@ ...@@ -77,11 +77,11 @@
'shouldRenderCalloutMessage', 'shouldRenderCalloutMessage',
'shouldRenderTriggeredLabel', 'shouldRenderTriggeredLabel',
'hasEnvironment', 'hasEnvironment',
'isJobStuck',
'hasTrace', 'hasTrace',
'emptyStateIllustration', 'emptyStateIllustration',
'isScrollingDown', 'isScrollingDown',
'emptyStateAction', 'emptyStateAction',
'hasRunnersForProject',
]), ]),
shouldRenderContent() { shouldRenderContent() {
...@@ -195,9 +195,9 @@ ...@@ -195,9 +195,9 @@
<!-- Body Section --> <!-- Body Section -->
<stuck-block <stuck-block
v-if="isJobStuck" v-if="job.stuck"
class="js-job-stuck" class="js-job-stuck"
:has-no-runners-for-project="job.runners.available" :has-no-runners-for-project="hasRunnersForProject"
:tags="job.tags" :tags="job.tags"
:runners-path="runnerSettingsUrl" :runners-path="runnerSettingsUrl"
/> />
......
...@@ -23,14 +23,7 @@ export default { ...@@ -23,14 +23,7 @@ export default {
<template> <template>
<div class="bs-callout bs-callout-warning"> <div class="bs-callout bs-callout-warning">
<p <p
v-if="hasNoRunnersForProject" v-if="tags.length"
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"
class="js-stuck-with-tags append-bottom-0" class="js-stuck-with-tags append-bottom-0"
> >
{{ s__(`This job is stuck, because you don't have {{ s__(`This job is stuck, because you don't have
...@@ -43,6 +36,13 @@ export default { ...@@ -43,6 +36,13 @@ export default {
{{ tag }} {{ tag }}
</span> </span>
</p> </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 <p
v-else v-else
class="js-stuck-no-active-runner append-bottom-0" class="js-stuck-no-active-runner append-bottom-0"
......
...@@ -41,17 +41,10 @@ export const emptyStateIllustration = state => ...@@ -41,17 +41,10 @@ export const emptyStateIllustration = state =>
(state.job && state.job.status && state.job.status.illustration) || {}; (state.job && state.job.status && state.job.status.illustration) || {};
export const emptyStateAction = state => (state.job && state.job.status && state.job.status.action) || {}; 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 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 // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -5,6 +5,7 @@ class BuildDetailsEntity < JobEntity ...@@ -5,6 +5,7 @@ class BuildDetailsEntity < JobEntity
expose :tag_list, as: :tags expose :tag_list, as: :tags
expose :has_trace?, as: :has_trace expose :has_trace?, as: :has_trace
expose :stage expose :stage
expose :stuck?, as: :stuck
expose :user, using: UserEntity expose :user, using: UserEntity
expose :runner, using: RunnerEntity expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity 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 ...@@ -297,6 +297,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response).to match_response_schema('job/job_details') expect(response).to match_response_schema('job/job_details')
expect(json_response['runners']['online']).to be false expect(json_response['runners']['online']).to be false
expect(json_response['runners']['available']).to be false expect(json_response['runners']['available']).to be false
expect(json_response['stuck']).to be true
end end
end end
...@@ -309,6 +310,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -309,6 +310,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response).to match_response_schema('job/job_details') expect(response).to match_response_schema('job/job_details')
expect(json_response['runners']['online']).to be false expect(json_response['runners']['online']).to be false
expect(json_response['runners']['available']).to be true expect(json_response['runners']['available']).to be true
expect(json_response['stuck']).to be true
end end
end end
......
...@@ -721,6 +721,62 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do ...@@ -721,6 +721,62 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
expect(page).not_to have_css('.js-job-sidebar.right-sidebar-collpased') expect(page).not_to have_css('.js-job-sidebar.right-sidebar-collpased')
end end
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 end
describe "POST /:project/jobs/:id/cancel", :js do describe "POST /:project/jobs/:id/cancel", :js do
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
"runner": { "$ref": "runner.json" }, "runner": { "$ref": "runner.json" },
"runners": { "$ref": "runners.json" }, "runners": { "$ref": "runners.json" },
"has_trace": { "type": "boolean" }, "has_trace": { "type": "boolean" },
"stage": { "type": "string" } "stage": { "type": "string" },
"stuck": { "type": "boolean" }
} }
} }
...@@ -88,7 +88,9 @@ describe('Job App ', () => { ...@@ -88,7 +88,9 @@ describe('Job App ', () => {
describe('triggered job', () => { describe('triggered job', () => {
beforeEach(() => { 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 }); vm = mountComponentWithStore(Component, { props, store });
}); });
...@@ -133,57 +135,106 @@ describe('Job App ', () => { ...@@ -133,57 +135,106 @@ describe('Job App ', () => {
}); });
describe('stuck block', () => { describe('stuck block', () => {
it('renders stuck block when there are no runners', done => { describe('without active runners availabl', () => {
mock.onGet(props.endpoint).replyOnce( it('renders stuck block when there are no runners', done => {
200, mock.onGet(props.endpoint).replyOnce(
Object.assign({}, job, { 200,
status: { Object.assign({}, job, {
group: 'pending', status: {
icon: 'status_pending', group: 'pending',
label: 'pending', icon: 'status_pending',
text: 'pending', label: 'pending',
details_path: 'path', text: 'pending',
}, details_path: 'path',
runners: { },
available: false, stuck: true,
}, runners: {
}), available: false,
); online: false,
vm = mountComponentWithStore(Component, { props, store }); },
tags: [],
setTimeout(() => { }),
expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull(); );
vm = mountComponentWithStore(Component, { props, store });
done(); setTimeout(() => {
}, 0); 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 => { describe('when available runners can not run specified tag', () => {
mock.onGet(props.endpoint).replyOnce( it('renders tags in stuck block when there are no runners', done => {
200, mock.onGet(props.endpoint).replyOnce(
Object.assign({}, job, { 200,
status: { Object.assign({}, job, {
group: 'pending', status: {
icon: 'status_pending', group: 'pending',
label: 'pending', icon: 'status_pending',
text: 'pending', label: 'pending',
details_path: 'path', text: 'pending',
}, details_path: 'path',
runners: { },
available: false, stuck: true,
}, runners: {
}), available: false,
); online: false,
},
}),
);
vm = mountComponentWithStore(Component, { vm = mountComponentWithStore(Component, {
props, props,
store, 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(() => { describe('when runners are offline and build has tags', () => {
expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]); it('renders message about job being stuck because of no runners with the specified tags', done => {
done(); mock.onGet(props.endpoint).replyOnce(
}, 0); 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 => { it('does not renders stuck block when there are no runners', done => {
...@@ -418,10 +469,11 @@ describe('Job App ', () => { ...@@ -418,10 +469,11 @@ describe('Job App ', () => {
vm.$store.state.trace = 'Update'; vm.$store.state.trace = 'Update';
setTimeout(() => { setTimeout(() => {
expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain('Update'); expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain(
expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain( 'Update',
'Different',
); );
expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Different');
done(); done();
}, 0); }, 0);
}); });
......
...@@ -175,43 +175,37 @@ describe('Job Store Getters', () => { ...@@ -175,43 +175,37 @@ describe('Job Store Getters', () => {
}); });
}); });
describe('isJobStuck', () => { describe('hasRunnersForProject', () => {
describe('when job is pending and runners are not available', () => { describe('with available and offline runners', () => {
it('returns true', () => { it('returns true', () => {
localState.job.status = {
group: 'pending',
};
localState.job.runners = { 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', () => { it('returns false', () => {
localState.job.status = {
group: 'running',
};
localState.job.runners = { localState.job.runners = {
available: false, 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', () => { it('returns false', () => {
localState.job.status = {
group: 'pending',
};
localState.job.runners = { 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