Commit 70ddce37 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 3113e821602c
# frozen_string_literal: true
class Admin::JobsController < Admin::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def index
# We need all builds for tabs counters
@all_builds = JobsFinder.new(current_user: current_user).execute
@scope = params[:scope]
@all_builds = Ci::Build
@builds = @all_builds.order('id DESC')
@builds =
case @scope
when 'pending'
@builds.pending.reverse_order
when 'running'
@builds.running.reverse_order
when 'finished'
@builds.finished
else
@builds
end
@builds = JobsFinder.new(current_user: current_user, params: params).execute
@builds = @builds.eager_load_everything
@builds = @builds.page(params[:page]).per(30)
end
# rubocop: enable CodeReuse/ActiveRecord
def cancel_all
Ci::Build.running_or_pending.each(&:cancel)
......
......@@ -17,34 +17,15 @@ class Projects::JobsController < Projects::ApplicationController
layout 'project'
# rubocop: disable CodeReuse/ActiveRecord
def index
# We need all builds for tabs counters
@all_builds = JobsFinder.new(current_user: current_user, project: @project).execute
@scope = params[:scope]
@all_builds = project.builds.relevant
@builds = @all_builds.order('ci_builds.id DESC')
@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 = JobsFinder.new(current_user: current_user, project: @project, params: params).execute
@builds = @builds.eager_load_everything
@builds = @builds.page(params[:page]).per(30).without_count
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
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 @@ def persisted_environment
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
joins(:metadata).merge(Ci::BuildMetadata.with_exposed_artifacts)
.includes(:metadata, :job_artifacts_metadata)
......@@ -161,6 +175,7 @@ def persisted_environment
end
scope :queued_before, ->(time) { where(arel_table[:queued_at].lt(time)) }
scope :order_id_desc, -> { order('ci_builds.id DESC') }
acts_as_taggable
......
......@@ -772,18 +772,10 @@ def merge_request_pipeline?
triggered_by_merge_request? && target_sha.present?
end
def merge_train_pipeline?
merge_request_pipeline? && merge_train_ref?
end
def merge_request_ref?
MergeRequest.merge_request_ref?(ref)
end
def merge_train_ref?
MergeRequest.merge_train_ref?(ref)
end
def matches_sha_or_source_sha?(sha)
self.sha == sha || self.source_sha == sha
end
......@@ -816,9 +808,7 @@ def merge_request_event_type
return unless merge_request_event?
strong_memoize(:merge_request_event_type) do
if merge_train_pipeline?
:merge_train
elsif merge_request_pipeline?
if merge_request_pipeline?
:merged_result
elsif detached_merge_request_pipeline?
:detached
......
---
title: Use correct fragment identifier for vulnerability help path
merge_request: 20524
author:
type: fixed
......@@ -9,16 +9,16 @@ that will create:
- A deb package for Ubuntu 16.04, available as a build artifact, and
- A docker image, which is pushed to [Omnibus GitLab's container
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).
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
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
......
......@@ -5257,14 +5257,15 @@ msgid_plural "CycleAnalytics|%d projects selected"
msgstr[0] ""
msgstr[1] ""
msgid "CycleAnalytics|%{stageName}"
msgid_plural "CycleAnalytics|%d stages selected"
msgstr[0] ""
msgstr[1] ""
msgid "CycleAnalytics|%{stageCount} stages selected"
msgstr ""
msgid "CycleAnalytics|All stages"
msgstr ""
msgid "CycleAnalytics|No stages selected"
msgstr ""
msgid "CycleAnalytics|Stages"
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 '~/behaviors/markdown/render_gfm';
import Vue from 'vue';
import AxiosMockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { mount, createLocalVue } from '@vue/test-utils';
import fieldComponent from '~/vue_shared/components/markdown/field.vue';
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) {
expect(writeLink.parentNode.classList.contains('active')).toEqual(isWrite);
expect(previewLink.parentNode.classList.contains('active')).toEqual(!isWrite);
expect(vm.$el.querySelector('.md-preview-holder').style.display).toEqual(isWrite ? 'none' : '');
const markdownPreviewPath = `${TEST_HOST}/preview`;
const markdownDocsPath = `${TEST_HOST}/docs`;
function assertMarkdownTabs(isWrite, writeLink, previewLink, wrapper) {
expect(writeLink.element.parentNode.classList.contains('active')).toEqual(isWrite);
expect(previewLink.element.parentNode.classList.contains('active')).toEqual(!isWrite);
expect(wrapper.find('.md-preview-holder').element.style.display).toEqual(isWrite ? 'none' : '');
}
function createComponent() {
const wrapper = mount(fieldComponent, {
propsData: {
markdownDocsPath,
markdownPreviewPath,
},
slots: {
textarea: '<textarea>testing\n123</textarea>',
},
template: `
<field-component
markdown-preview-path="${markdownPreviewPath}"
markdown-docs-path="${markdownDocsPath}"
>
<textarea
slot="textarea"
v-model="text">
<slot>this is a test</slot>
</textarea>
</field-component>
`,
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');
describe('Markdown field component', () => {
const markdownPreviewPath = `${TEST_HOST}/preview`;
const markdownDocsPath = `${TEST_HOST}/docs`;
let axiosMock;
let vm;
const localVue = createLocalVue();
beforeEach(done => {
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
vm = new Vue({
components: {
fieldComponent,
},
data() {
return {
text: 'testing\n123',
};
},
template: `
<field-component
markdown-preview-path="${markdownPreviewPath}"
markdown-docs-path="${markdownDocsPath}"
>
<textarea
slot="textarea"
v-model="text">
</textarea>
</field-component>
`,
}).$mount();
Vue.nextTick(done);
});
afterEach(() => {
......@@ -50,122 +58,120 @@ describe('Markdown field component', () => {
});
describe('mounted', () => {
let wrapper;
const previewHTML = '<p>markdown preview</p>';
let previewLink;
let writeLink;
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', () => {
let previewLink;
let writeLink;
beforeEach(() => {
axiosMock.onPost(markdownPreviewPath).replyOnce(200, { body: previewHTML });
previewLink = vm.$el.querySelector('.nav-links .js-preview-link');
writeLink = vm.$el.querySelector('.nav-links .js-write-link');
axiosMock.onPost(markdownPreviewPath).reply(200, { body: previewHTML });
});
it('sets preview link as active', done => {
previewLink.click();
Vue.nextTick(() => {
expect(previewLink.parentNode.classList.contains('active')).toBeTruthy();
it('sets preview link as active', () => {
wrapper = createComponent();
previewLink = getPreviewLink(wrapper);
previewLink.trigger('click');
done();
return localVue.nextTick().then(() => {
expect(previewLink.element.parentNode.classList.contains('active')).toBeTruthy();
});
});
it('shows preview loading text', done => {
previewLink.click();
it('shows preview loading text', () => {
wrapper = createComponent();
previewLink = getPreviewLink(wrapper);
previewLink.trigger('click');
Vue.nextTick(() => {
expect(vm.$el.querySelector('.md-preview-holder').textContent.trim()).toContain(
localVue.nextTick(() => {
expect(wrapper.find('.md-preview-holder').element.textContent.trim()).toContain(
'Loading…',
);
done();
});
});
it('renders markdown preview', done => {
previewLink.click();
it('renders markdown preview', () => {
wrapper = createComponent();
previewLink = getPreviewLink(wrapper);
previewLink.trigger('click');
setTimeout(() => {
expect(vm.$el.querySelector('.md-preview-holder').innerHTML).toContain(previewHTML);
done();
expect(wrapper.find('.md-preview-holder').element.innerHTML).toContain(previewHTML);
});
});
it('renders GFM with jQuery', done => {
spyOn($.fn, 'renderGFM');
it('renders GFM with jQuery', () => {
wrapper = createComponent();
previewLink = getPreviewLink(wrapper);
jest.spyOn($.fn, 'renderGFM');
previewLink.click();
previewLink.trigger('click');
setTimeout(() => {
expect($.fn.renderGFM).toHaveBeenCalled();
done();
}, 0);
});
it('clicking already active write or preview link does nothing', done => {
writeLink.click();
Vue.nextTick()
.then(() => assertMarkdownTabs(true, writeLink, previewLink, vm))
.then(() => writeLink.click())
.then(() => Vue.nextTick())
.then(() => assertMarkdownTabs(true, writeLink, previewLink, vm))
.then(() => previewLink.click())
.then(() => Vue.nextTick())
.then(() => assertMarkdownTabs(false, writeLink, previewLink, vm))
.then(() => previewLink.click())
.then(() => Vue.nextTick())
.then(() => assertMarkdownTabs(false, writeLink, previewLink, vm))
.then(done)
.catch(done.fail);
it('clicking already active write or preview link does nothing', () => {
wrapper = createComponent();
writeLink = getWriteLink(wrapper);
previewLink = getPreviewLink(wrapper);
writeLink.trigger('click');
return localVue
.nextTick()
.then(() => assertMarkdownTabs(true, writeLink, previewLink, wrapper))
.then(() => writeLink.trigger('click'))
.then(() => localVue.nextTick())
.then(() => assertMarkdownTabs(true, writeLink, previewLink, wrapper))
.then(() => previewLink.trigger('click'))
.then(() => localVue.nextTick())
.then(() => assertMarkdownTabs(false, writeLink, previewLink, wrapper))
.then(() => previewLink.trigger('click'))
.then(() => localVue.nextTick())
.then(() => assertMarkdownTabs(false, writeLink, previewLink, wrapper));
});
});
describe('markdown buttons', () => {
it('converts single words', done => {
const textarea = vm.$el.querySelector('textarea');
it('converts single words', () => {
wrapper = createComponent();
const textarea = wrapper.find('textarea').element;
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**');
done();
});
});
it('converts a line', done => {
const textarea = vm.$el.querySelector('textarea');
it('converts a line', () => {
wrapper = createComponent();
const textarea = wrapper.find('textarea').element;
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');
done();
});
});
it('converts multiple lines', done => {
const textarea = vm.$el.querySelector('textarea');