Commit 61392168 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch '40739-access-404' into 'master'

Prevent job link form rendering when user does not have permissions

Closes #40739

See merge request gitlab-org/gitlab-ce!15723
parents f3a3bd50 387f1626
...@@ -78,11 +78,13 @@ ...@@ -78,11 +78,13 @@
<div class="ci-job-component"> <div class="ci-job-component">
<a <a
v-tooltip v-tooltip
v-if="job.status.details_path" v-if="job.status.has_details"
:href="job.status.details_path" :href="job.status.details_path"
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
data-container="body"> data-container="body"
class="js-pipeline-graph-job-link"
>
<job-name-component <job-name-component
:name="job.name" :name="job.name"
...@@ -95,7 +97,8 @@ ...@@ -95,7 +97,8 @@
v-tooltip v-tooltip
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
data-container="body"> data-container="body"
>
<job-name-component <job-name-component
:name="job.name" :name="job.name"
......
...@@ -185,6 +185,36 @@ describe 'Pipeline', :js do ...@@ -185,6 +185,36 @@ describe 'Pipeline', :js do
end end
end end
context 'when user does not have access to read jobs' do
before do
project.update(public_builds: false)
end
describe 'GET /:project/pipelines/:id' do
include_context 'pipeline builds'
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) }
before do
visit project_pipeline_path(project, pipeline)
end
it 'shows the pipeline graph' do
expect(page).to have_selector('.pipeline-visualization')
expect(page).to have_content('Build')
expect(page).to have_content('Test')
expect(page).to have_content('Deploy')
expect(page).to have_content('Retry')
expect(page).to have_content('Cancel running')
end
it 'should not link to job' do
expect(page).not_to have_selector('.js-pipeline-graph-job-link')
end
end
end
describe 'GET /:project/pipelines/:id/builds' do describe 'GET /:project/pipelines/:id/builds' do
include_context 'pipeline builds' include_context 'pipeline builds'
......
import Vue from 'vue'; import Vue from 'vue';
import jobComponent from '~/pipelines/components/graph/job_component.vue'; import jobComponent from '~/pipelines/components/graph/job_component.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('pipeline graph job component', () => { describe('pipeline graph job component', () => {
let JobComponent; let JobComponent;
let component;
const mockJob = { const mockJob = {
id: 4256, id: 4256,
...@@ -13,6 +15,7 @@ describe('pipeline graph job component', () => { ...@@ -13,6 +15,7 @@ describe('pipeline graph job component', () => {
label: 'passed', label: 'passed',
group: 'success', group: 'success',
details_path: '/root/ci-mock/builds/4256', details_path: '/root/ci-mock/builds/4256',
has_details: true,
action: { action: {
icon: 'retry', icon: 'retry',
title: 'Retry', title: 'Retry',
...@@ -26,13 +29,13 @@ describe('pipeline graph job component', () => { ...@@ -26,13 +29,13 @@ describe('pipeline graph job component', () => {
JobComponent = Vue.extend(jobComponent); JobComponent = Vue.extend(jobComponent);
}); });
afterEach(() => {
component.$destroy();
});
describe('name with link', () => { describe('name with link', () => {
it('should render the job name and status with a link', (done) => { it('should render the job name and status with a link', (done) => {
const component = new JobComponent({ component = mountComponent(JobComponent, { job: mockJob });
propsData: {
job: mockJob,
},
}).$mount();
Vue.nextTick(() => { Vue.nextTick(() => {
const link = component.$el.querySelector('a'); const link = component.$el.querySelector('a');
...@@ -56,8 +59,7 @@ describe('pipeline graph job component', () => { ...@@ -56,8 +59,7 @@ describe('pipeline graph job component', () => {
describe('name without link', () => { describe('name without link', () => {
it('it should render status and name', () => { it('it should render status and name', () => {
const component = new JobComponent({ component = mountComponent(JobComponent, {
propsData: {
job: { job: {
id: 4256, id: 4256,
name: 'test', name: 'test',
...@@ -67,12 +69,13 @@ describe('pipeline graph job component', () => { ...@@ -67,12 +69,13 @@ describe('pipeline graph job component', () => {
label: 'passed', label: 'passed',
group: 'success', group: 'success',
details_path: '/root/ci-mock/builds/4256', details_path: '/root/ci-mock/builds/4256',
has_details: false,
}, },
}, },
}, });
}).$mount();
expect(component.$el.querySelector('.js-status-icon-success')).toBeDefined(); expect(component.$el.querySelector('.js-status-icon-success')).toBeDefined();
expect(component.$el.querySelector('a')).toBeNull();
expect( expect(
component.$el.querySelector('.ci-status-text').textContent.trim(), component.$el.querySelector('.ci-status-text').textContent.trim(),
...@@ -82,11 +85,7 @@ describe('pipeline graph job component', () => { ...@@ -82,11 +85,7 @@ describe('pipeline graph job component', () => {
describe('action icon', () => { describe('action icon', () => {
it('it should render the action icon', () => { it('it should render the action icon', () => {
const component = new JobComponent({ component = mountComponent(JobComponent, { job: mockJob });
propsData: {
job: mockJob,
},
}).$mount();
expect(component.$el.querySelector('a.ci-action-icon-container')).toBeDefined(); expect(component.$el.querySelector('a.ci-action-icon-container')).toBeDefined();
expect(component.$el.querySelector('i.ci-action-icon-wrapper')).toBeDefined(); expect(component.$el.querySelector('i.ci-action-icon-wrapper')).toBeDefined();
...@@ -95,24 +94,20 @@ describe('pipeline graph job component', () => { ...@@ -95,24 +94,20 @@ describe('pipeline graph job component', () => {
describe('dropdown', () => { describe('dropdown', () => {
it('should render the dropdown action icon', () => { it('should render the dropdown action icon', () => {
const component = new JobComponent({ component = mountComponent(JobComponent, {
propsData: {
job: mockJob, job: mockJob,
isDropdown: true, isDropdown: true,
}, });
}).$mount();
expect(component.$el.querySelector('a.ci-action-icon-wrapper')).toBeDefined(); expect(component.$el.querySelector('a.ci-action-icon-wrapper')).toBeDefined();
}); });
}); });
it('should render provided class name', () => { it('should render provided class name', () => {
const component = new JobComponent({ component = mountComponent(JobComponent, {
propsData: {
job: mockJob, job: mockJob,
cssClassJobName: 'css-class-job-name', cssClassJobName: 'css-class-job-name',
}, });
}).$mount();
expect( expect(
component.$el.querySelector('a').classList.contains('css-class-job-name'), component.$el.querySelector('a').classList.contains('css-class-job-name'),
......
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