Commit f1bb2a30 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent ad1e4b8f
......@@ -58,6 +58,7 @@ export default {
<template>
<div class="ide-stage card prepend-top-default">
<div
ref="cardHeader"
:class="{
'border-bottom-0': stage.isCollapsed,
}"
......@@ -79,7 +80,7 @@ export default {
</div>
<icon :name="collapseIcon" class="ide-stage-collapse-icon" />
</div>
<div v-show="!stage.isCollapsed" class="card-body">
<div v-show="!stage.isCollapsed" ref="jobList" class="card-body">
<gl-loading-icon v-if="showLoadingIcon" />
<template v-else>
<item v-for="job in stage.jobs" :key="job.id" :job="job" @clickViewLog="clickViewLog" />
......
......@@ -95,10 +95,10 @@ export class SearchAutocomplete {
this.createAutocomplete();
}
this.searchInput.addClass('disabled');
this.saveTextLength();
this.bindEvents();
this.dropdownToggle.dropdown();
this.searchInput.addClass('js-autocomplete-disabled');
}
// Finds an element inside wrapper element
......@@ -338,7 +338,7 @@ export class SearchAutocomplete {
if (!this.dropdown.hasClass('show')) {
this.loadingSuggestions = false;
this.dropdownToggle.dropdown('toggle');
return this.searchInput.removeClass('disabled');
return this.searchInput.removeClass('js-autocomplete-disabled');
}
}
......@@ -432,8 +432,8 @@ export class SearchAutocomplete {
}
disableAutocomplete() {
if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('show')) {
this.searchInput.addClass('disabled');
if (!this.searchInput.hasClass('js-autocomplete-disabled') && this.dropdown.hasClass('show')) {
this.searchInput.addClass('js-autocomplete-disabled');
this.dropdown.removeClass('show').trigger('hidden.bs.dropdown');
this.restoreMenu();
}
......
# frozen_string_literal: true
module RedirectsForMissingPathOnTree
def redirect_to_tree_root_for_missing_path(project, ref, path)
redirect_to project_tree_path(project, ref), notice: missing_path_on_ref(path, ref)
end
private
def missing_path_on_ref(path, ref)
_('"%{path}" did not exist on "%{ref}"') % { path: truncate_path(path), ref: ref }
end
def truncate_path(path)
path.reverse.truncate(60, separator: "/").reverse
end
end
......@@ -3,6 +3,7 @@
# Controller for viewing a file's blame
class Projects::BlameController < Projects::ApplicationController
include ExtractsPath
include RedirectsForMissingPathOnTree
before_action :require_non_empty_project
before_action :assign_ref_vars
......@@ -11,7 +12,9 @@ class Projects::BlameController < Projects::ApplicationController
def show
@blob = @repository.blob_at(@commit.id, @path)
return render_404 unless @blob
unless @blob
return redirect_to_tree_root_for_missing_path(@project, @ref, @path)
end
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
......
......@@ -7,6 +7,7 @@ class Projects::BlobController < Projects::ApplicationController
include RendersBlob
include NotesHelper
include ActionView::Helpers::SanitizeHelper
include RedirectsForMissingPathOnTree
prepend_before_action :authenticate_user!, only: [:edit]
around_action :allow_gitaly_ref_name_caching, only: [:show]
......@@ -119,7 +120,7 @@ class Projects::BlobController < Projects::ApplicationController
end
end
return render_404
return redirect_to_tree_root_for_missing_path(@project, @ref, @path)
end
end
......
......@@ -12,7 +12,7 @@ class Projects::JobsController < Projects::ApplicationController
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action only: [:show] do
push_frontend_feature_flag(:job_log_json)
push_frontend_feature_flag(:job_log_json, project)
end
layout 'project'
......
......@@ -5,6 +5,7 @@ class Projects::TreeController < Projects::ApplicationController
include ExtractsPath
include CreatesCommit
include ActionView::Helpers::SanitizeHelper
include RedirectsForMissingPathOnTree
around_action :allow_gitaly_ref_name_caching, only: [:show]
......@@ -19,12 +20,9 @@ class Projects::TreeController < Projects::ApplicationController
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
return redirect_to(
project_blob_path(@project,
File.join(@ref, @path))
)
return redirect_to project_blob_path(@project, File.join(@ref, @path))
elsif @path.present?
return render_404
return redirect_to_tree_root_for_missing_path(@project, @ref, @path)
end
end
......
---
title: When a user views a file's blame or blob and switches to a branch where the
current file does not exist, they will now be redirected to the root of the repository.
merge_request: 18169
author: Jesse Hall @jessehall3
type: changed
---
title: Bump Gitaly to 1.70.0 and remove cache invalidation feature flag
merge_request: 18766
author:
type: other
---
title: Fix incorrect selection of custom templates
merge_request: 17205
author:
type: fixed
......@@ -3,71 +3,17 @@
class SetSelfMonitoringProjectAlertingToken < ActiveRecord::Migration[5.2]
DOWNTIME = false
module Migratable
module Alerting
class ProjectAlertingSetting < ApplicationRecord
self.table_name = 'project_alerting_settings'
belongs_to :project
validates :token, presence: true
attr_encrypted :token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm'
before_validation :ensure_token
private
def ensure_token
self.token ||= generate_token
end
def generate_token
SecureRandom.hex
end
end
end
class Project < ApplicationRecord
has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting'
end
class ApplicationSetting < ApplicationRecord
self.table_name = 'application_settings'
belongs_to :instance_administration_project, class_name: 'Project'
def self.current_without_cache
last
end
end
end
def setup_alertmanager_token(project)
return unless License.feature_available?(:prometheus_alerts)
project.create_alerting_setting!
end
def up
Gitlab.ee do
project = Migratable::ApplicationSetting.current_without_cache&.instance_administration_project
# no-op
# Converted to no-op in https://gitlab.com/gitlab-org/gitlab/merge_requests/17049.
if project
setup_alertmanager_token(project)
end
end
# This migration has been made a no-op because the pre-requisite migration
# which creates the self-monitoring project has already been removed in
# https://gitlab.com/gitlab-org/gitlab/merge_requests/16864. As
# such, this migration would do nothing.
end
def down
Gitlab.ee do
Migratable::ApplicationSetting.current_without_cache
&.instance_administration_project
&.alerting_setting
&.destroy!
end
# no-op
end
end
......@@ -83,6 +83,7 @@ This was originally implemented in: <https://gitlab.com/gitlab-org/gitlab-foss/m
- In JS tests, shifting elements can cause Capybara to misclick when the element moves at the exact time Capybara sends the click
- [Dropdowns rendering upward or downward due to window size and scroll position](https://gitlab.com/gitlab-org/gitlab/merge_requests/17660)
- [Lazy loaded images can cause Capybara to misclick](https://gitlab.com/gitlab-org/gitlab/merge_requests/18713)
- [Triggering JS events before the event handlers are set up](https://gitlab.com/gitlab-org/gitlab/merge_requests/18742)
#### Capybara viewport size related issues
......
# Generic alerts integration **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/13203) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.3.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/13203) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.
GitLab can accept alerts from any source via a generic webhook receiver.
When you set up the generic alerts integration, a unique endpoint will
......@@ -16,7 +16,7 @@ authored by the GitLab Alert Bot.
To set up the generic alerts integration:
1. Navigate to **Settings > Integrations** in a project.
1. Click on **Alert endpoint**.
1. Click on **Alerts endpoint**.
1. Toggle the **Active** alert setting. The `URL` and `Authorization Key` for the webhook configuration can be found there.
## Customizing the payload
......
......@@ -7,7 +7,6 @@ class Feature
# Server feature flags should use '_' to separate words.
SERVER_FEATURE_FLAGS =
%w[
cache_invalidator
inforef_uploadpack_cache
get_all_lfs_pointers_go
].freeze
......
......@@ -62,6 +62,9 @@ msgstr ""
msgid " or references (e.g. path/to/project!merge_request_id)"
msgstr ""
msgid "\"%{path}\" did not exist on \"%{ref}\""
msgstr ""
msgid "%d comment"
msgid_plural "%d comments"
msgstr[0] ""
......
......@@ -36,7 +36,6 @@ module QA
runner.tags = tags
runner.image = image
runner.config = config if config
runner.run_untagged = true
runner.register!
end
end
......
# frozen_string_literal: true
require 'spec_helper'
describe RedirectsForMissingPathOnTree, type: :controller do
controller(ActionController::Base) do
include Gitlab::Routing.url_helpers
include RedirectsForMissingPathOnTree
def fake
redirect_to_tree_root_for_missing_path(Project.find(params[:project_id]), params[:ref], params[:file_path])
end
end
let(:project) { create(:project) }
before do
routes.draw { get 'fake' => 'anonymous#fake' }
end
describe '#redirect_to_root_path' do
it 'redirects to the tree path with a notice' do
long_file_path = ('a/b/' * 30) + 'foo.txt'
truncated_file_path = '...b/' + ('a/b/' * 12) + 'foo.txt'
expected_message = "\"#{truncated_file_path}\" did not exist on \"theref\""
get :fake, params: { project_id: project.id, ref: 'theref', file_path: long_file_path }
expect(response).to redirect_to project_tree_path(project, 'theref')
expect(response.flash[:notice]).to eq(expected_message)
end
end
end
......@@ -25,14 +25,25 @@ describe Projects::BlameController do
})
end
context "valid file" do
context "valid branch, valid file" do
let(:id) { 'master/files/ruby/popen.rb' }
it { is_expected.to respond_with(:success) }
end
context "invalid file" do
let(:id) { 'master/files/ruby/missing_file.rb'}
it { expect(response).to have_gitlab_http_status(404) }
context "valid branch, invalid file" do
let(:id) { 'master/files/ruby/invalid-path.rb' }
it 'redirects' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/master")
end
end
context "invalid branch, valid file" do
let(:id) { 'invalid-branch/files/ruby/missing_file.rb'}
it { is_expected.to respond_with(:not_found) }
end
end
end
......@@ -24,26 +24,34 @@ describe Projects::BlobController do
context "valid branch, valid file" do
let(:id) { 'master/README.md' }
it { is_expected.to respond_with(:success) }
end
context "valid branch, invalid file" do
let(:id) { 'master/invalid-path.rb' }
it { is_expected.to respond_with(:not_found) }
it 'redirects' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/master")
end
end
context "invalid branch, valid file" do
let(:id) { 'invalid-branch/README.md' }
it { is_expected.to respond_with(:not_found) }
end
context "binary file" do
let(:id) { 'binary-encoding/encoding/binary-1.bin' }
it { is_expected.to respond_with(:success) }
end
context "Markdown file" do
let(:id) { 'master/README.md' }
it { is_expected.to respond_with(:success) }
end
end
......@@ -104,6 +112,7 @@ describe Projects::BlobController do
context 'redirect to tree' do
let(:id) { 'markdown/doc' }
it 'redirects' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/markdown/doc")
......
......@@ -30,46 +30,61 @@ describe Projects::TreeController do
context "valid branch, no path" do
let(:id) { 'master' }
it { is_expected.to respond_with(:success) }
end
context "valid branch, valid path" do
let(:id) { 'master/encoding/' }
it { is_expected.to respond_with(:success) }
end
context "valid branch, invalid path" do
let(:id) { 'master/invalid-path/' }
it { is_expected.to respond_with(:not_found) }
it 'redirects' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/master")
end
end
context "invalid branch, valid path" do
let(:id) { 'invalid-branch/encoding/' }
it { is_expected.to respond_with(:not_found) }
end
context "valid empty branch, invalid path" do
let(:id) { 'empty-branch/invalid-path/' }
it { is_expected.to respond_with(:not_found) }
it 'redirects' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/empty-branch")
end
end
context "valid empty branch" do
let(:id) { 'empty-branch' }
it { is_expected.to respond_with(:success) }
end
context "invalid SHA commit ID" do
let(:id) { 'ff39438/.gitignore' }
it { is_expected.to respond_with(:not_found) }
end
context "valid SHA commit ID" do
let(:id) { '6d39438' }
it { is_expected.to respond_with(:success) }
end
context "valid SHA commit ID with path" do
let(:id) { '6d39438/.gitignore' }
it { expect(response).to have_gitlab_http_status(302) }
end
end
......@@ -108,6 +123,7 @@ describe Projects::TreeController do
context 'redirect to blob' do
let(:id) { 'master/README.md' }
it 'redirects' do
redirect_url = "/#{project.full_path}/blob/master/README.md"
expect(subject)
......
......@@ -28,8 +28,7 @@ describe 'User uses header search field', :js do
context 'when clicking the search field' do
before do
page.find('#search').click
wait_for_all_requests
page.find('#search.js-autocomplete-disabled').click
end
it 'shows category search dropdown' do
......
......@@ -36,10 +36,18 @@ describe TodosFinder do
expect(todos).to match_array([todo1, todo2])
end
it 'returns correct todos when filtered by a type' do
todos = finder.new(user, { type: 'Issue' }).execute
context 'when filtering by type' do
it 'returns correct todos when filtered by a type' do
todos = finder.new(user, { type: 'Issue' }).execute
expect(todos).to match_array([todo1])
expect(todos).to match_array([todo1])
end
it 'returns the correct todos when filtering for multiple types' do
todos = finder.new(user, { type: %w[Issue MergeRequest] }).execute
expect(todos).to match_array([todo1, todo2])
end
end
context 'when filtering for actions' do
......@@ -53,12 +61,10 @@ describe TodosFinder do
expect(todos).to match_array([todo2])
end
context 'multiple actions' do
it 'returns the expected todos' do
todos = finder.new(user, { action_id: [Todo::DIRECTLY_ADDRESSED, Todo::ASSIGNED] }).execute
it 'returns the expected todos when filtering for multiple action ids' do
todos = finder.new(user, { action_id: [Todo::DIRECTLY_ADDRESSED, Todo::ASSIGNED] }).execute
expect(todos).to match_array([todo2, todo1])
end
expect(todos).to match_array([todo2, todo1])
end
end
......@@ -69,12 +75,10 @@ describe TodosFinder do
expect(todos).to match_array([todo2])
end
context 'multiple actions' do
it 'returns the expected todos' do
todos = finder.new(user, { action: [:directly_addressed, :assigned] }).execute
it 'returns the expected todos when filtering for multiple action names' do
todos = finder.new(user, { action: [:directly_addressed, :assigned] }).execute
expect(todos).to match_array([todo2, todo1])
end
expect(todos).to match_array([todo2, todo1])
end
end
end
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`IDE pipeline stage renders stage details & icon 1`] = `
<div
class="ide-stage card prepend-top-default"
>
<div
class="card-header"
>
<ciicon-stub
cssclasses=""
size="24"
status="[object Object]"
/>
<strong
class="prepend-left-8 ide-stage-title"
data-container="body"
data-original-title=""
title=""
>
build
</strong>
<div
class="append-right-8 prepend-left-4"
>
<span
class="badge badge-pill"
>
4
</span>
</div>
<icon-stub
class="ide-stage-collapse-icon"
name="angle-down"
size="16"
/>
</div>
<div
class="card-body"
>
<item-stub
job="[object Object]"
/>
<item-stub
job="[object Object]"
/>
<item-stub
job="[object Object]"
/>
<item-stub
job="[object Object]"
/>
</div>
</div>
`;
import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import Stage from '~/ide/components/jobs/stage.vue';
import Item from '~/ide/components/jobs/item.vue';
import { stages, jobs } from '../../mock_data';
describe('IDE pipeline stage', () => {
let wrapper;
const defaultProps = {
stage: {
...stages[0],
id: 0,
dropdownPath: stages[0].dropdown_path,
jobs: [...jobs],
isLoading: false,
isCollapsed: false,
},
};
const findHeader = () => wrapper.find({ ref: 'cardHeader' });
const findJobList = () => wrapper.find({ ref: 'jobList' });
const createComponent = props => {
wrapper = shallowMount(Stage, {
propsData: {
...defaultProps,
...props,
},
sync: false,
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('emits fetch event when mounted', () => {
createComponent();
expect(wrapper.emitted().fetch).toBeDefined();
});
it('renders loading icon when no jobs and isLoading is true', () => {
createComponent({
stage: { ...defaultProps.stage, isLoading: true, jobs: [] },
});
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
it('emits toggleCollaped event with stage id when clicking header', () => {
const id = 5;
createComponent({ stage: { ...defaultProps.stage, id } });
findHeader().trigger('click');
expect(wrapper.emitted().toggleCollapsed[0][0]).toBe(id);
});
it('emits clickViewLog entity with job', () => {
const [job] = defaultProps.stage.jobs;
createComponent();
wrapper
.findAll(Item)
.at(0)
.vm.$emit('clickViewLog', job);
expect(wrapper.emitted().clickViewLog[0][0]).toBe(job);
});
it('renders stage details & icon', () => {
createComponent();
expect(wrapper.element).toMatchSnapshot();
});
describe('when collapsed', () => {
beforeEach(() => {
createComponent({ stage: { ...defaultProps.stage, isCollapsed: true } });
});
it('does not render job list', () => {
expect(findJobList().isVisible()).toBe(false);
});
it('sets border bottom class', () => {
expect(findHeader().classes('border-bottom-0')).toBe(true);
});
});
});
import Vue from 'vue';
import Stage from '~/ide/components/jobs/stage.vue';
import { stages, jobs } from '../../mock_data';
describe('IDE pipeline stage', () => {
const Component = Vue.extend(Stage);
let vm;
let stage;
beforeEach(() => {
stage = {
...stages[0],
id: 0,
dropdownPath: stages[0].dropdown_path,
jobs: [...jobs],
isLoading: false,
isCollapsed: false,
};
vm = new Component({
propsData: { stage },
});
spyOn(vm, '$emit');
vm.$mount();
});
afterEach(() => {
vm.$destroy();
});
it('emits fetch event when mounted', () => {
expect(vm.$emit).toHaveBeenCalledWith('fetch', vm.stage);
});
it('renders stages details', () => {
expect(vm.$el.textContent).toContain(vm.stage.name);
});
it('renders CI icon', () => {
expect(vm.$el.querySelector('.ic-status_failed')).not.toBe(null);
});
describe('collapsed', () => {
it('emits event when clicking header', done => {
vm.$el.querySelector('.card-header').click();
vm.$nextTick(() => {
expect(vm.$emit).toHaveBeenCalledWith('toggleCollapsed', vm.stage.id);
done();
});
});
it('toggles collapse status when collapsed', done => {
vm.stage.isCollapsed = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.card-body').style.display).toBe('none');
done();
});
});
it('sets border bottom class when collapsed', done => {
vm.stage.isCollapsed = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.card-header').classList).toContain('border-bottom-0');
done();
});
});
});
it('renders jobs count', () => {
expect(vm.$el.querySelector('.badge').textContent).toContain('4');
});
it('renders loading icon when no jobs and isLoading is true', done => {
vm.stage.isLoading = true;
vm.stage.jobs = [];
vm.$nextTick(() => {
expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
done();
});
});
it('renders list of jobs', () => {
expect(vm.$el.querySelectorAll('.ide-job-item').length).toBe(4);
});
});
......@@ -843,8 +843,8 @@ describe 'Git HTTP requests' do
get "/#{project.full_path}/blob/master/info/refs"
end
it "returns not found" do
expect(response).to have_gitlab_http_status(:not_found)
it "redirects" do
expect(response).to have_gitlab_http_status(302)
end
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