BigW Consortium Gitlab

Commit 93c372a7 by Robert Speicher

Merge branch '10-0-stable-prepare-rc6' into '10-0-stable'

Prepare 10.0 RC6 release See merge request gitlab-org/gitlab-ce!14424
parents 2ae38580 65befd85
......@@ -128,7 +128,7 @@ gem 'asciidoctor-plantuml', '0.0.7'
gem 'rouge', '~> 2.0'
gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0'
gem 'nokogiri', '~> 1.8.0'
gem 'nokogiri', '~> 1.8.1'
# Diffs
gem 'diffy', '~> 3.1.0'
......
......@@ -482,7 +482,7 @@ GEM
mime-types (2.99.3)
mimemagic (0.3.0)
mini_mime (0.1.4)
mini_portile2 (2.2.0)
mini_portile2 (2.3.0)
minitest (5.7.0)
mmap2 (2.2.7)
mousetrap-rails (1.4.6)
......@@ -496,8 +496,8 @@ GEM
net-ldap (0.16.0)
net-ssh (4.1.0)
netrc (0.11.0)
nokogiri (1.8.0)
mini_portile2 (~> 2.2.0)
nokogiri (1.8.1)
mini_portile2 (~> 2.3.0)
numerizer (0.1.1)
oauth (0.5.1)
oauth2 (1.4.0)
......@@ -1067,7 +1067,7 @@ DEPENDENCIES
mysql2 (~> 0.4.5)
net-ldap
net-ssh (~> 4.1.0)
nokogiri (~> 1.8.0)
nokogiri (~> 1.8.1)
oauth2 (~> 1.4)
octokit (~> 4.6.2)
oj (~> 2.17.4)
......
......@@ -375,8 +375,6 @@ header.navbar-gitlab-new {
display: flex;
width: 100%;
position: relative;
padding-top: $gl-padding;
padding-bottom: $gl-padding;
align-items: center;
border-bottom: 1px solid $border-color;
}
......@@ -388,6 +386,11 @@ header.navbar-gitlab-new {
align-self: center;
color: $gl-text-color-secondary;
@media (max-width: $screen-xs-max) {
padding-left: 17px;
border-left: 1px solid $gl-text-color-quaternary;
}
.avatar-tile {
margin-right: 4px;
border: 1px solid $border-color;
......
......@@ -433,9 +433,8 @@ $new-sidebar-collapsed-width: 50px;
background-color: transparent;
border: 0;
padding: 6px 16px;
margin: 0 16px 0 -15px;
margin: 0 0 0 -15px;
height: 46px;
border-right: 1px solid $gl-text-color-quaternary;
i {
font-size: 20px;
......@@ -443,7 +442,12 @@ $new-sidebar-collapsed-width: 50px;
}
@media (max-width: $screen-xs-max) {
display: inline-block;
display: flex;
align-items: center;
i {
font-size: 18px;
}
}
}
......
......@@ -727,6 +727,12 @@ ul.notes {
border-bottom-left-radius: 0;
}
.btn {
svg path {
fill: $gray-darkest;
}
}
.btn.discussion-create-issue-btn {
margin-left: -4px;
border-radius: 0;
......@@ -741,10 +747,6 @@ ul.notes {
border: 0;
}
}
.new-issue-for-discussion path {
fill: $gray-darkest;
}
}
}
......@@ -817,16 +819,6 @@ ul.notes {
vertical-align: middle;
}
.discussion-next-btn {
svg {
margin: 0;
path {
fill: $gray-darkest;
}
}
}
// Merge request notes in diffs
.diff-file {
// Diff is inline
......
......@@ -84,9 +84,9 @@ class Projects::IssuesController < Projects::ApplicationController
.inc_relations_for_view
.includes(:noteable)
.fresh
.reject { |n| n.cross_reference_not_visible_for?(current_user) }
prepare_notes_for_rendering(notes)
notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
discussions = Discussion.build_collection(notes, @issue)
......
class Event < ActiveRecord::Base
include Sortable
include IgnorableColumn
default_scope { reorder(nil).where.not(author_id: nil) }
default_scope { reorder(nil) }
CREATED = 1
UPDATED = 2
......@@ -77,6 +77,12 @@ class Event < ActiveRecord::Base
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
# Authors are required as they're used to display who pushed data.
#
# We're just validating the presence of the ID here as foreign key constraints
# should ensure the ID points to a valid user.
validates :author_id, presence: true
self.inheritance_column = 'action'
# "data" will be removed in 10.0 but it may be possible that JOINs happen that
......
......@@ -278,8 +278,6 @@ class Issue < ActiveRecord::Base
end
def update_project_counter_caches
return unless update_project_counter_caches?
Projects::OpenIssuesCountService.new(project).refresh_cache
end
......
......@@ -955,8 +955,6 @@ class MergeRequest < ActiveRecord::Base
end
def update_project_counter_caches
return unless update_project_counter_caches?
Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache
end
......
......@@ -146,7 +146,7 @@ class ProjectTeam
def member?(user, min_access_level = Gitlab::Access::GUEST)
return false unless user
user.authorized_project?(project, min_access_level)
max_member_access(user.id) >= min_access_level
end
def human_max_access(user_id)
......
......@@ -3,12 +3,6 @@ class PushEvent < Event
# different "action" value.
validate :validate_push_action
# Authors are required as they're used to display who pushed data.
#
# We're just validating the presence of the ID here as foreign key constraints
# should ensure the ID points to a valid user.
validates :author_id, presence: true
# The project is required to build links to commits, commit ranges, etc.
#
# We're just validating the presence of the ID here as foreign key constraints
......
......@@ -182,6 +182,7 @@ class IssuableBaseService < BaseService
after_create(issuable)
execute_hooks(issuable)
invalidate_cache_counts(issuable, users: issuable.assignees)
issuable.update_project_counter_caches
end
issuable
......@@ -193,8 +194,6 @@ class IssuableBaseService < BaseService
def after_create(issuable)
# To be overridden by subclasses
issuable.update_project_counter_caches
end
def before_update(issuable)
......@@ -203,8 +202,6 @@ class IssuableBaseService < BaseService
def after_update(issuable)
# To be overridden by subclasses
issuable.update_project_counter_caches
end
def update(issuable)
......@@ -229,6 +226,10 @@ class IssuableBaseService < BaseService
before_update(issuable)
# We have to perform this check before saving the issuable as Rails resets
# the changed fields upon calling #save.
update_project_counters = issuable.update_project_counter_caches?
if issuable.with_transaction_returning_status { issuable.save }
# We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do
......@@ -249,6 +250,8 @@ class IssuableBaseService < BaseService
after_update(issuable)
issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update')
issuable.update_project_counter_caches if update_project_counters
end
end
......
......@@ -29,6 +29,7 @@ module Issues
todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close')
invalidate_cache_counts(issue, users: issue.assignees)
issue.update_project_counter_caches
end
issue
......
......@@ -14,6 +14,7 @@ module MergeRequests
todo_service.close_merge_request(merge_request, current_user)
execute_hooks(merge_request, 'close')
invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches
end
merge_request
......
......@@ -2,6 +2,11 @@ module Projects
# Base class for the various service classes that count project data (e.g.
# issues or forks).
class CountService
# The version of the cache format. This should be bumped whenever the
# underlying logic changes. This removes the need for explicitly flushing
# all caches.
VERSION = 1
def initialize(project)
@project = project
end
......@@ -37,7 +42,7 @@ module Projects
end
def cache_key
['projects', @project.id, cache_key_name]
['projects', 'count_service', VERSION, @project.id, cache_key_name]
end
end
end
......@@ -10,9 +10,6 @@
= render "projects/last_push"
%div{ class: container_class }
- if show_callout?('user_callout_dismissed')
= render 'shared/user_callout'
- if has_projects_or_name?(@projects, params)
= render 'dashboard/projects_head'
= render 'projects'
......
- if merge_request.discussions_can_be_resolved_by?(current_user) && can?(current_user, :create_issue, @project)
.btn-group{ role: "group", "v-if" => "unresolvedDiscussionCount > 0" }
.btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve all discussions in new issue",
"aria-label" => "Resolve all discussions in a new issue",
"data-container" => "body" }
= link_to custom_icon('icon_mr_issue'), new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid), title: "Resolve all discussions in new issue", class: 'new-issue-for-discussion'
= link_to custom_icon('icon_mr_issue'),
new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid),
title: 'Resolve all discussions in new issue',
aria: { label: 'Resolve all discussions in new issue' },
data: { container: 'body' },
class: 'new-issue-for-discussion btn btn-default discussion-create-issue-btn has-tooltip'
......@@ -2,7 +2,9 @@
%new-issue-for-discussion-btn{ ":discussion-id" => "'#{discussion.id}'",
"inline-template" => true }
.btn-group{ role: "group", "v-if" => "showButton" }
.btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve this discussion in a new issue",
"aria-label" => "Resolve this discussion in a new issue",
"data-container" => "body" }
= link_to custom_icon('icon_mr_issue'), new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id), title: "Resolve this discussion in a new issue", class: 'new-issue-for-discussion'
= link_to custom_icon('icon_mr_issue'),
new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id),
title: 'Resolve this discussion in a new issue',
aria: { label: 'Resolve this discussion in a new issue' },
data: { container: 'body' },
class: 'new-issue-for-discussion btn btn-default discussion-create-issue-btn has-tooltip'
.user-callout{ data: { uid: 'user_callout_dismissed' } }
.bordered-box.landing.content-block
%button.btn.btn-default.close.js-close-callout{ type: 'button',
'aria-label' => 'Dismiss customize experience box' }
= icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true')
.svg-container
= custom_icon('icon_customization')
.user-callout-copy
%h4
Customize your experience
%p
Change syntax themes, default project pages, and more in preferences.
= link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary js-close-callout'
......@@ -99,8 +99,6 @@
Snippets
%div{ class: container_class }
- if @user == current_user && show_callout?('user_callout_dismissed')
= render 'shared/user_callout'
.tab-content
#activity.tab-pane
.row-content-block.calender-block.white.second-block.hidden-xs
......
---
title: Fix the "resolve discussion in a new issue" button
merge_request: 14357
author:
type: fixed
---
title: Remove redundant WHERE from event queries
merge_request:
author:
type: other
---
title: Eliminate N+1 queries in loading discussions.json endpoint
merge_request:
author:
type: fixed
---
title: Eliminate N+1 queries referencing issues
merge_request:
author:
type: fixed
......@@ -32,19 +32,17 @@ This is the typeface used for code blocks and references to commits, branches, a
---
## Icons
GitLab uses Font Awesome icons throughout our interface.
| | |
| :-----------: | :---- |
| ![Trash icon](img/icon-trash.png) | The trash icon is used for destructive actions that deletes information. |
| ![Edit icon](img/icon-edit.png) | The pencil icon is used for editing content such as comments.|
| ![Notification icon](img/icon-notification.png) | The bell icon is for notifications, such as Todos. |
| ![Subscribe icon](img/icon-subscribe.png) | The eye icon is for subscribing to updates. For example, you can subscribe to a label and get updated on issues with that label. |
| ![RSS icon](img/icon-rss.png) | The standard RSS icon is used for linking to RSS/atom feeds. |
| ![Close icon](img/icon-close.png) | An 'x' is used for closing UI elements such as dropdowns. |
| ![Add icon](img/icon-add.png) | A plus is used when creating new objects, such as issues, projects, etc. |
> TODO: update this section, add more general guidance to icon usage and personality, etc.
GitLab has a strong, unique personality. When you look at any screen, you should know immediately know that it is GitLab.
Iconography is a powerful visual cue to the user and is a great way for us to reflect our particular sense of style.
- **Standard size:** 16px * 16px
- **Border thickness:** 2px
- **Border radius:** 3px
![Icon sampler](img/icon-spec.png)
> TODO: List all icons, proper usage, hover, and active states.
---
......
# Auto DevOps: quick start guide
> [Introduced][ce-37115] in GitLab 10.0. Auto DevOps is currently in Beta and
**not recommended for production use**.
DANGER: Auto DevOps is currently in **Beta** and _not recommended for production use_.
> [Introduced][ce-37115] in GitLab 10.0.
This is a step-by-step guide to deploying a project hosted on GitLab.com to
Google Cloud, using Auto DevOps.
We made a minimal [Ruby
application](https://gitlab.com/gitlab-examples/minimal-ruby-app) to use as an
example for this guide. It contains two files:
application](https://gitlab.com/auto-devops-examples/minimal-ruby-app) to use
as an example for this guide. It contains two main files:
* `server.rb` - our application. It will start an HTTP server on port 5000 and
render "Hello, world!"
......@@ -113,11 +114,9 @@ assigned to the cluster IP.
In your GitLab.com project, go to **Settings > CI/CD** and find the Auto DevOps
section. Select "Enable Auto DevOps", add in your base domain, and save.
![auto devops settings](img/auto_devops_settings.png)
Next, a pipeline needs to be triggered. Since the test project doesn't have a
`.gitlab-ci.yml`, you need to either push a change to the repository or
manually visit `https://gitlab.com/<username>/minimal-ruby-app/pipelines/run`,
manually visit `https://gitlab.com/<username>/minimal-ruby-app/pipelines/new`,
where `<username>` is your username.
This will create a new pipeline with several jobs: `build`, `test`, `codequality`,
......
......@@ -34,7 +34,8 @@ module Banzai
{ namespace: :owner },
{ group: [:owners, :group_members] },
:invited_groups,
:project_members
:project_members,
:project_feature
]
}
),
......
......@@ -900,5 +900,37 @@ describe Projects::IssuesController do
expect(JSON.parse(response.body).first.keys).to match_array(%w[id reply_id expanded notes individual_note])
end
context 'with cross-reference system note', :request_store do
let(:new_issue) { create(:issue) }
let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" }
before do
create(:discussion_note_on_issue, :system, noteable: issue, project: issue.project, note: cross_reference)
end
it 'filters notes that the user should not see' do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
expect(JSON.parse(response.body).count).to eq(1)
end
it 'does not result in N+1 queries' do
# Instantiate the controller variables to ensure QueryRecorder has an accurate base count
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
RequestStore.clear!
control_count = ActiveRecord::QueryRecorder.new do
get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
end.count
RequestStore.clear!
create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference)
expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid }.not_to exceed_query_limit(control_count)
end
end
end
end
require 'spec_helper'
describe 'User Callouts', js: true do
let(:user) { create(:user) }
let(:another_user) { create(:user) }
let(:project) { create(:project, path: 'gitlab', name: 'sample') }
before do
sign_in(user)
project.team << [user, :master]
end
it 'takes you to the profile preferences when the link is clicked' do
visit dashboard_projects_path
click_link 'Check it out'
expect(current_path).to eq profile_preferences_path
end
it 'does not show when cookie is set' do
visit dashboard_projects_path
within('.user-callout') do
find('.close').trigger('click')
end
visit dashboard_projects_path
expect(page).not_to have_selector('.user-callout')
end
describe 'user callout should appear in two routes' do
it 'shows up on the user profile' do
visit user_path(user)
expect(find('.user-callout')).to have_content 'Customize your experience'
end
it 'shows up on the dashboard projects' do
visit dashboard_projects_path
expect(find('.user-callout')).to have_content 'Customize your experience'
end
end
it 'hides the user callout when click on the dismiss icon' do
visit user_path(user)
within('.user-callout') do
find('.close').click
end
expect(page).not_to have_selector('.user-callout')
end
it 'does not show callout on another users profile' do
visit user_path(another_user)
expect(page).not_to have_selector('.user-callout')
end
end
require 'spec_helper'
describe Dashboard::ProjectsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, namespace: namespace, path: 'builds-project') }
render_views
before(:all) do
clean_frontend_fixtures('dashboard/')
end
before do
sign_in(admin)
end
after do
remove_repository(project)
end
it 'dashboard/user-callout.html.raw' do |example|
rendered = render_template('shared/_user_callout')
store_frontend_fixture(rendered, example.description)
end
private
def render_template(template_file_name)
controller.prepend_view_path(JavaScriptFixturesHelpers::FIXTURE_PATH)
controller.render_to_string(template_file_name, layout: false)
end
end
import Cookies from 'js-cookie';
import UserCallout from '~/user_callout';
const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
describe('UserCallout', function () {
const fixtureName = 'dashboard/user-callout.html.raw';
preloadFixtures(fixtureName);
beforeEach(() => {
loadFixtures(fixtureName);
Cookies.remove(USER_CALLOUT_COOKIE);
this.userCallout = new UserCallout();
this.closeButton = $('.js-close-callout.close');
this.userCalloutBtn = $('.js-close-callout:not(.close)');
});
it('hides when user clicks on the dismiss-icon', (done) => {
this.closeButton.click();
expect(Cookies.get(USER_CALLOUT_COOKIE)).toBe('true');
setTimeout(() => {
expect(
document.querySelector('.user-callout'),
).toBeNull();
done();
});
});
it('hides when user clicks on the "check it out" button', () => {
this.userCalloutBtn.click();
expect(Cookies.get(USER_CALLOUT_COOKIE)).toBe('true');
});
describe('Sets cookie with setCalloutPerProject', () => {
beforeEach(() => {
spyOn(Cookies, 'set').and.callFake(() => {});
document.querySelector('.user-callout').setAttribute('data-project-path', 'foo/bar');
this.userCallout = new UserCallout({ setCalloutPerProject: true });
});
it('sets a cookie when the user clicks the close button', () => {
this.userCalloutBtn.click();
expect(Cookies.set).toHaveBeenCalledWith('user_callout_dismissed', 'true', Object({ expires: 365, path: 'foo/bar' }));
});
});
});
......@@ -42,7 +42,7 @@ describe Issues::CloseService do
service.execute(issue)
end
it 'refreshes the number of open issues' do
it 'refreshes the number of open issues', :use_clean_rails_memory_store_caching do
expect { service.execute(issue) }
.to change { project.open_issues_count }.from(1).to(0)
end
......
......@@ -35,7 +35,7 @@ describe Issues::CreateService do
expect(issue.due_date).to eq Date.tomorrow
end
it 'refreshes the number of open issues' do
it 'refreshes the number of open issues', :use_clean_rails_memory_store_caching do
expect { issue }.to change { project.open_issues_count }.from(0).to(1)
end
......
......@@ -64,6 +64,13 @@ describe Issues::UpdateService, :mailer do
expect(issue.due_date).to eq Date.tomorrow
end
it 'refreshes the number of open issues when the issue is made confidential', :use_clean_rails_memory_store_caching do
issue # make sure the issue is created first so our counts are correct.
expect { update_issue(confidential: true) }
.to change { project.open_issues_count }.from(1).to(0)
end
it 'updates open issue counter for assignees when issue is reassigned' do
update_issue(assignee_ids: [user2.id])
......
......@@ -52,7 +52,7 @@ describe MergeRequests::CloseService do
end
end
it 'refreshes the number of open merge requests for a valid MR' do
it 'refreshes the number of open merge requests for a valid MR', :use_clean_rails_memory_store_caching do
service = described_class.new(project, user, {})
expect { service.execute(merge_request) }
......
......@@ -37,7 +37,7 @@ describe MergeRequests::CreateService do
expect(service).to have_received(:execute_hooks).with(merge_request)
end
it 'refreshes the number of open merge requests' do
it 'refreshes the number of open merge requests', :use_clean_rails_memory_store_caching do
expect { service.execute }
.to change { project.open_merge_requests_count }.from(0).to(1)
end
......
......@@ -66,8 +66,8 @@ describe Projects::CountService do
describe '#cache_key' do
it 'returns the cache key as an Array' do
allow(service).to receive(:cache_key_name).and_return('count_service')
expect(service.cache_key).to eq(['projects', 1, 'count_service'])
allow(service).to receive(:cache_key_name).and_return('foo')
expect(service.cache_key).to eq(['projects', 'count_service', described_class::VERSION, 1, 'foo'])
end
end
end
......@@ -271,10 +271,6 @@ production:
--version="$CI_PIPELINE_ID-$CI_JOB_ID" \
"$name" \
chart/
if [[ "$track" == "stable" ]]; then
kubectl rollout status -n "$KUBE_NAMESPACE" -w "deployment/${CI_ENVIRONMENT_SLUG}-auto-deploy"
fi
}
function install_dependencies() {
......
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