Commit 96157369 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent eb7390ed
# frozen_string_literal: true # frozen_string_literal: true
class Admin::JobsController < Admin::ApplicationController class Admin::JobsController < Admin::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def index def index
# We need all builds for tabs counters
@all_builds = JobsFinder.new(current_user: current_user).execute
@scope = params[:scope] @scope = params[:scope]
@all_builds = Ci::Build @builds = JobsFinder.new(current_user: current_user, params: params).execute
@builds = @all_builds.order('id DESC') @builds = @builds.eager_load_everything
@builds =
case @scope
when 'pending'
@builds.pending.reverse_order
when 'running'
@builds.running.reverse_order
when 'finished'
@builds.finished
else
@builds
end
@builds = @builds.page(params[:page]).per(30) @builds = @builds.page(params[:page]).per(30)
end end
# rubocop: enable CodeReuse/ActiveRecord
def cancel_all def cancel_all
Ci::Build.running_or_pending.each(&:cancel) Ci::Build.running_or_pending.each(&:cancel)
......
...@@ -17,34 +17,15 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -17,34 +17,15 @@ class Projects::JobsController < Projects::ApplicationController
layout 'project' layout 'project'
# rubocop: disable CodeReuse/ActiveRecord
def index def index
# We need all builds for tabs counters
@all_builds = JobsFinder.new(current_user: current_user, project: @project).execute
@scope = params[:scope] @scope = params[:scope]
@all_builds = project.builds.relevant @builds = JobsFinder.new(current_user: current_user, project: @project, params: params).execute
@builds = @all_builds.order('ci_builds.id DESC') @builds = @builds.eager_load_everything
@builds =
case @scope
when 'pending'
@builds.pending.reverse_order
when 'running'
@builds.running.reverse_order
when 'finished'
@builds.finished
else
@builds
end
@builds = @builds.includes([
{ pipeline: [:project, :user] },
:job_artifacts_archive,
:metadata,
:trigger_request,
:project,
:user,
:tags
])
@builds = @builds.page(params[:page]).per(30).without_count @builds = @builds.page(params[:page]).per(30).without_count
end end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def show def show
......
# frozen_string_literal: true
class JobsFinder
include Gitlab::Allowable
def initialize(current_user:, project: nil, params: {})
@current_user = current_user
@project = project
@params = params
end
def execute
builds = init_collection.order_id_desc
filter_by_scope(builds)
rescue Gitlab::Access::AccessDeniedError
Ci::Build.none
end
private
attr_reader :current_user, :project, :params
def init_collection
project ? project_builds : all_builds
end
def all_builds
raise Gitlab::Access::AccessDeniedError unless current_user&.admin?
Ci::Build.all
end
def project_builds
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :read_build, project)
project.builds.relevant
end
def filter_by_scope(builds)
case params[:scope]
when 'pending'
builds.pending.reverse_order
when 'running'
builds.running.reverse_order
when 'finished'
builds.finished
else
builds
end
end
end
...@@ -120,6 +120,20 @@ module Ci ...@@ -120,6 +120,20 @@ module Ci
scope :eager_load_job_artifacts, -> { includes(:job_artifacts) } scope :eager_load_job_artifacts, -> { includes(:job_artifacts) }
scope :eager_load_everything, -> do
includes(
[
{ pipeline: [:project, :user] },
:job_artifacts_archive,
:metadata,
:trigger_request,
:project,
:user,
:tags
]
)
end
scope :with_exposed_artifacts, -> do scope :with_exposed_artifacts, -> do
joins(:metadata).merge(Ci::BuildMetadata.with_exposed_artifacts) joins(:metadata).merge(Ci::BuildMetadata.with_exposed_artifacts)
.includes(:metadata, :job_artifacts_metadata) .includes(:metadata, :job_artifacts_metadata)
...@@ -161,6 +175,7 @@ module Ci ...@@ -161,6 +175,7 @@ module Ci
end end
scope :queued_before, ->(time) { where(arel_table[:queued_at].lt(time)) } scope :queued_before, ->(time) { where(arel_table[:queued_at].lt(time)) }
scope :order_id_desc, -> { order('ci_builds.id DESC') }
acts_as_taggable acts_as_taggable
......
...@@ -772,18 +772,10 @@ module Ci ...@@ -772,18 +772,10 @@ module Ci
triggered_by_merge_request? && target_sha.present? triggered_by_merge_request? && target_sha.present?
end end
def merge_train_pipeline?
merge_request_pipeline? && merge_train_ref?
end
def merge_request_ref? def merge_request_ref?
MergeRequest.merge_request_ref?(ref) MergeRequest.merge_request_ref?(ref)
end end
def merge_train_ref?
MergeRequest.merge_train_ref?(ref)
end
def matches_sha_or_source_sha?(sha) def matches_sha_or_source_sha?(sha)
self.sha == sha || self.source_sha == sha self.sha == sha || self.source_sha == sha
end end
...@@ -816,9 +808,7 @@ module Ci ...@@ -816,9 +808,7 @@ module Ci
return unless merge_request_event? return unless merge_request_event?
strong_memoize(:merge_request_event_type) do strong_memoize(:merge_request_event_type) do
if merge_train_pipeline? if merge_request_pipeline?
:merge_train
elsif merge_request_pipeline?
:merged_result :merged_result
elsif detached_merge_request_pipeline? elsif detached_merge_request_pipeline?
:detached :detached
......
---
title: Use correct fragment identifier for vulnerability help path
merge_request: 20524
author:
type: fixed
...@@ -9,16 +9,16 @@ that will create: ...@@ -9,16 +9,16 @@ that will create:
- A deb package for Ubuntu 16.04, available as a build artifact, and - A deb package for Ubuntu 16.04, available as a build artifact, and
- A docker image, which is pushed to [Omnibus GitLab's container - A docker image, which is pushed to [Omnibus GitLab's container
registry](https://gitlab.com/gitlab-org/omnibus-gitlab/container_registry) registry](https://gitlab.com/gitlab-org/omnibus-gitlab/container_registry)
(images titled `gitlab-foss` and `gitlab-ee` respectively and image tag is the (images titled `gitlab-ce` and `gitlab-ee` respectively and image tag is the
commit which triggered the pipeline). commit which triggered the pipeline).
When you push a commit to either the GitLab CE or GitLab EE project, the When you push a commit to either the GitLab CE or GitLab EE project, the
pipeline for that commit will have a `build-package` manual action you can pipeline for that commit will have a `build-package` manual action you can
trigger. trigger.
![Manual actions](img/trigger_ss1.png) ![Manual actions](img/build_package_v12_6.png)
![Build package manual action](img/trigger_ss2.png) ![Build package manual action](img/trigger_build_package_v12_6.png)
## Specifying versions of components ## Specifying versions of components
......
...@@ -5257,14 +5257,15 @@ msgid_plural "CycleAnalytics|%d projects selected" ...@@ -5257,14 +5257,15 @@ msgid_plural "CycleAnalytics|%d projects selected"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "CycleAnalytics|%{stageName}" msgid "CycleAnalytics|%{stageCount} stages selected"
msgid_plural "CycleAnalytics|%d stages selected" msgstr ""
msgstr[0] ""
msgstr[1] ""
msgid "CycleAnalytics|All stages" msgid "CycleAnalytics|All stages"
msgstr "" msgstr ""
msgid "CycleAnalytics|No stages selected"
msgstr ""
msgid "CycleAnalytics|Stages" msgid "CycleAnalytics|Stages"
msgstr "" msgstr ""
......
# frozen_string_literal: true
require 'spec_helper'
describe JobsFinder, '#execute' do
set(:user) { create(:user) }
set(:admin) { create(:user, :admin) }
set(:project) { create(:project, :private, public_builds: false) }
set(:pipeline) { create(:ci_pipeline, project: project) }
set(:job_1) { create(:ci_build) }
set(:job_2) { create(:ci_build, :running) }
set(:job_3) { create(:ci_build, :success, pipeline: pipeline) }
let(:params) { {} }
context 'no project' do
subject { described_class.new(current_user: admin, params: params).execute }
it 'returns all jobs' do
expect(subject).to match_array([job_1, job_2, job_3])
end
context 'non admin user' do
let(:admin) { user }
it 'returns no jobs' do
expect(subject).to be_empty
end
end
context 'without user' do
let(:admin) { nil }
it 'returns no jobs' do
expect(subject).to be_empty
end
end
context 'scope is present' do
let(:jobs) { [job_1, job_2, job_3] }
where(:scope, :index) do
[
['pending', 0],
['running', 1],
['finished', 2]
]
end
with_them do
let(:params) { { scope: scope } }
it { expect(subject).to match_array([jobs[index]]) }
end
end
end
context 'a project is present' do
subject { described_class.new(current_user: user, project: project, params: params).execute }
context 'user has access to the project' do
before do
project.add_maintainer(user)
end
it 'returns jobs for the specified project' do
expect(subject).to match_array([job_3])
end
end
context 'user has no access to project builds' do
before do
project.add_guest(user)
end
it 'returns no jobs' do
expect(subject).to be_empty
end
end
context 'without user' do
let(:user) { nil }
it 'returns no jobs' do
expect(subject).to be_empty
end
end
end
end
import $ from 'jquery'; import { mount, createLocalVue } from '@vue/test-utils';
import '~/behaviors/markdown/render_gfm';
import Vue from 'vue';
import AxiosMockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import fieldComponent from '~/vue_shared/components/markdown/field.vue'; import fieldComponent from '~/vue_shared/components/markdown/field.vue';
import { TEST_HOST } from 'spec/test_constants'; import { TEST_HOST } from 'spec/test_constants';
import AxiosMockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import $ from 'jquery';
function assertMarkdownTabs(isWrite, writeLink, previewLink, vm) { const markdownPreviewPath = `${TEST_HOST}/preview`;
expect(writeLink.parentNode.classList.contains('active')).toEqual(isWrite); const markdownDocsPath = `${TEST_HOST}/docs`;
expect(previewLink.parentNode.classList.contains('active')).toEqual(!isWrite);
expect(vm.$el.querySelector('.md-preview-holder').style.display).toEqual(isWrite ? 'none' : '');
}
describe('Markdown field component', () => { function assertMarkdownTabs(isWrite, writeLink, previewLink, wrapper) {
const markdownPreviewPath = `${TEST_HOST}/preview`; expect(writeLink.element.parentNode.classList.contains('active')).toEqual(isWrite);
const markdownDocsPath = `${TEST_HOST}/docs`; expect(previewLink.element.parentNode.classList.contains('active')).toEqual(!isWrite);
let axiosMock; expect(wrapper.find('.md-preview-holder').element.style.display).toEqual(isWrite ? 'none' : '');
let vm; }
beforeEach(done => { function createComponent() {
axiosMock = new AxiosMockAdapter(axios); const wrapper = mount(fieldComponent, {
vm = new Vue({ propsData: {
components: { markdownDocsPath,
fieldComponent, markdownPreviewPath,
}, },
data() { slots: {
return { textarea: '<textarea>testing\n123</textarea>',
text: 'testing\n123',
};
}, },
template: ` template: `
<field-component <field-component
...@@ -37,12 +31,26 @@ describe('Markdown field component', () => { ...@@ -37,12 +31,26 @@ describe('Markdown field component', () => {
<textarea <textarea
slot="textarea" slot="textarea"
v-model="text"> v-model="text">
<slot>this is a test</slot>
</textarea> </textarea>
</field-component> </field-component>
`, `,
}).$mount(); sync: false,
});
return wrapper;
}
const getPreviewLink = wrapper => wrapper.find('.nav-links .js-preview-link');
const getWriteLink = wrapper => wrapper.find('.nav-links .js-write-link');
const getMarkdownButton = wrapper => wrapper.find('.js-md');
const getAllMarkdownButtons = wrapper => wrapper.findAll('.js-md');
Vue.nextTick(done); describe('Markdown field component', () => {
let axiosMock;
const localVue = createLocalVue();
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
}); });
afterEach(() => { afterEach(() => {
...@@ -50,122 +58,120 @@ describe('Markdown field component', () => { ...@@ -50,122 +58,120 @@ describe('Markdown field component', () => {
}); });
describe('mounted', () => { describe('mounted', () => {
let wrapper;
const previewHTML = '<p>markdown preview</p>'; const previewHTML = '<p>markdown preview</p>';
let previewLink;
let writeLink;
it('renders textarea inside backdrop', () => { it('renders textarea inside backdrop', () => {
expect(vm.$el.querySelector('.zen-backdrop textarea')).not.toBeNull(); wrapper = createComponent();
expect(wrapper.find('.zen-backdrop textarea').element).not.toBeNull();
}); });
describe('markdown preview', () => { describe('markdown preview', () => {
let previewLink;
let writeLink;
beforeEach(() => { beforeEach(() => {
axiosMock.onPost(markdownPreviewPath).replyOnce(200, { body: previewHTML }); axiosMock.onPost(markdownPreviewPath).reply(200, { body: previewHTML });
previewLink = vm.$el.querySelector('.nav-links .js-preview-link');
writeLink = vm.$el.querySelector('.nav-links .js-write-link');
}); });
it('sets preview link as active', done => { it('sets preview link as active', () => {
previewLink.click(); wrapper = createComponent();
previewLink = getPreviewLink(wrapper);
previewLink.trigger('click');
Vue.nextTick(() => { return localVue.nextTick().then(() => {
expect(previewLink.parentNode.classList.contains('active')).toBeTruthy(); expect(previewLink.element.parentNode.classList.contains('active')).toBeTruthy();
done();
}); });
}); });
it('shows preview loading text', done => { it('shows preview loading text', () => {
previewLink.click(); wrapper = createComponent();
previewLink = getPreviewLink(wrapper);
previewLink.trigger('click');
Vue.nextTick(() => { localVue.nextTick(() => {
expect(vm.$el.querySelector('.md-preview-holder').textContent.trim()).toContain( expect(wrapper.find('.md-preview-holder').element.textContent.trim()).toContain(
'Loading…', 'Loading…',
); );
done();
}); });
}); });
it('renders markdown preview', done => { it('renders markdown preview', () => {
previewLink.click(); wrapper = createComponent();
previewLink = getPreviewLink(wrapper);
previewLink.trigger('click');
setTimeout(() => { setTimeout(() => {
expect(vm.$el.querySelector('.md-preview-holder').innerHTML).toContain(previewHTML); expect(wrapper.find('.md-preview-holder').element.innerHTML).toContain(previewHTML);
done();
}); });
}); });
it('renders GFM with jQuery', done => { it('renders GFM with jQuery', () => {
spyOn($.fn, 'renderGFM'); wrapper = createComponent();
previewLink = getPreviewLink(wrapper);
jest.spyOn($.fn, 'renderGFM');
previewLink.click(); previewLink.trigger('click');
setTimeout(() => { setTimeout(() => {
expect($.fn.renderGFM).toHaveBeenCalled(); expect($.fn.renderGFM).toHaveBeenCalled();
done();
}, 0); }, 0);
}); });
it('clicking already active write or preview link does nothing', done => { it('clicking already active write or preview link does nothing', () => {
writeLink.click(); wrapper = createComponent();
Vue.nextTick() writeLink = getWriteLink(wrapper);
.then(() => assertMarkdownTabs(true, writeLink, previewLink, vm)) previewLink = getPreviewLink(wrapper);
.then(() => writeLink.click())
.then(() => Vue.nextTick()) writeLink.trigger('click');
.then(() => assertMarkdownTabs(true, writeLink, previewLink, vm)) return localVue
.then(() => previewLink.click()) .nextTick()
.then(() => Vue.nextTick()) .then(() => assertMarkdownTabs(true, writeLink, previewLink, wrapper))
.then(() => assertMarkdownTabs(false, writeLink, previewLink, vm)) .then(() => writeLink.trigger('click'))
.then(() => previewLink.click()) .then(() => localVue.nextTick())
.then(() => Vue.nextTick()) .then(() => assertMarkdownTabs(true, writeLink, previewLink, wrapper))
.then(() => assertMarkdownTabs(false, writeLink, previewLink, vm)) .then(() => previewLink.trigger('click'))
.then(done) .then(() => localVue.nextTick())
.catch(done.fail); .then(() => assertMarkdownTabs(false, writeLink, previewLink, wrapper))
.then(() => previewLink.trigger('click'))
.then(() => localVue.nextTick())
.then(() => assertMarkdownTabs(false, writeLink, previewLink, wrapper));
}); });
}); });
describe('markdown buttons', () => { describe('markdown buttons', () => {
it('converts single words', done => { it('converts single words', () => {
const textarea = vm.$el.querySelector('textarea'); wrapper = createComponent();
const textarea = wrapper.find('textarea').element;
textarea.setSelectionRange(0, 7); textarea.setSelectionRange(0, 7);
vm.$el.querySelector('.js-md').click(); const markdownButton = getMarkdownButton(wrapper);
markdownButton.trigger('click');
Vue.nextTick(() => { localVue.nextTick(() => {
expect(textarea.value).toContain('**testing**'); expect(textarea.value).toContain('**testing**');
done();
}); });
}); });
it('converts a line', done => { it('converts a line', () => {
const textarea = vm.$el.querySelector('textarea'); wrapper = createComponent();
const textarea = wrapper.find('textarea').element;
textarea.setSelectionRange(0, 0); textarea.setSelectionRange(0, 0);
vm.$el.querySelectorAll('.js-md')[5].click(); const markdownButton = getAllMarkdownButtons(wrapper).wrappers[5];
markdownButton.trigger('click');
Vue.nextTick(() => { localVue.nextTick(() => {
expect(textarea.value).toContain('* testing'); expect(textarea.value).toContain('* testing');
done();
}); });
}); });
it('converts multiple lines', done => { it('converts multiple lines', () => {
const textarea = vm.$el.querySelector('textarea'); wrapper = createComponent();
const textarea = wrapper.find('textarea').element;
textarea.setSelectionRange(0, 50); textarea.setSelectionRange(0, 50);
vm.$el.querySelectorAll('.js-md')[5].click(); const markdownButton = getAllMarkdownButtons(wrapper).wrappers[5];
markdownButton.trigger('click');
Vue.nextTick(() => { localVue.nextTick(() => {
expect(textarea.value).toContain('* testing\n* 123'); expect(textarea.value).toContain('* testing\n* 123');
done();
}); });
}); });
}); });
......
...@@ -199,25 +199,6 @@ describe Ci::Pipeline, :mailer do ...@@ -199,25 +199,6 @@ describe Ci::Pipeline, :mailer do
end end
end end
describe '#merge_train_pipeline?' do
subject { pipeline.merge_train_pipeline? }
let!(:pipeline) do
create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: ref, target_sha: 'xxx')
end
let(:merge_request) { create(:merge_request) }
let(:ref) { 'refs/merge-requests/1/train' }
it { is_expected.to be_truthy }
context 'when ref is merge ref' do
let(:ref) { 'refs/merge-requests/1/merge' }
it { is_expected.to be_falsy }
end
end
describe '#merge_request_ref?' do describe '#merge_request_ref?' do
subject { pipeline.merge_request_ref? } subject { pipeline.merge_request_ref? }
...@@ -228,43 +209,19 @@ describe Ci::Pipeline, :mailer do ...@@ -228,43 +209,19 @@ describe Ci::Pipeline, :mailer do
end end
end end
describe '#merge_train_ref?' do
subject { pipeline.merge_train_ref? }
it 'calls Mergetrain#merge_train_ref?' do
expect(MergeRequest).to receive(:merge_train_ref?).with(pipeline.ref)
subject
end
end
describe '#merge_request_event_type' do describe '#merge_request_event_type' do
subject { pipeline.merge_request_event_type } subject { pipeline.merge_request_event_type }
before do let(:pipeline) { merge_request.all_pipelines.last }
allow(pipeline).to receive(:merge_request_event?) { true }
end
context 'when pipeline is merge train pipeline' do
before do
allow(pipeline).to receive(:merge_train_pipeline?) { true }
end
it { is_expected.to eq(:merge_train) }
end
context 'when pipeline is merge request pipeline' do context 'when pipeline is merge request pipeline' do
before do let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
allow(pipeline).to receive(:merge_request_pipeline?) { true }
end
it { is_expected.to eq(:merged_result) } it { is_expected.to eq(:merged_result) }
end end
context 'when pipeline is detached merge request pipeline' do context 'when pipeline is detached merge request pipeline' do
before do let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
allow(pipeline).to receive(:detached_merge_request_pipeline?) { true }
end
it { is_expected.to eq(:detached) } it { is_expected.to eq(:detached) }
end end
......
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