...@@ -18,5 +18,3 @@ export const timeWindows = { ...@@ -18,5 +18,3 @@ export const timeWindows = {
threeDays: __('3 days'), threeDays: __('3 days'),
oneWeek: __('1 week'), oneWeek: __('1 week'),
}; };
export const msPerMinute = 60000;
import { timeWindows, msPerMinute } from './constants'; import { timeWindows } from './constants';
/** /**
* method that converts a predetermined time window to minutes * method that converts a predetermined time window to minutes
...@@ -6,27 +6,26 @@ import { timeWindows, msPerMinute } from './constants'; ...@@ -6,27 +6,26 @@ import { timeWindows, msPerMinute } from './constants';
* @param {String} timeWindow - The time window to convert to minutes * @param {String} timeWindow - The time window to convert to minutes
* @returns {number} The time window in minutes * @returns {number} The time window in minutes
*/ */
const getTimeDifferenceMinutes = timeWindow => { const getTimeDifferenceSeconds = timeWindow => {
switch (timeWindow) { switch (timeWindow) {
case timeWindows.thirtyMinutes: case timeWindows.thirtyMinutes:
return 30; return 60 * 30;
case timeWindows.threeHours: case timeWindows.threeHours:
return 60 * 3; return 60 * 60 * 3;
case timeWindows.oneDay: case timeWindows.oneDay:
return 60 * 24 * 1; return 60 * 60 * 24 * 1;
case timeWindows.threeDays: case timeWindows.threeDays:
return 60 * 24 * 3; return 60 * 60 * 24 * 3;
case timeWindows.oneWeek: case timeWindows.oneWeek:
return 60 * 24 * 7 * 1; return 60 * 60 * 24 * 7 * 1;
default: default:
return 60 * 8; return 60 * 60 * 8;
} }
}; };
export const getTimeDiff = selectedTimeWindow => { export const getTimeDiff = selectedTimeWindow => {
const end = Date.now(); const end = Date.now() / 1000; // convert milliseconds to seconds
const timeDifferenceMinutes = getTimeDifferenceMinutes(selectedTimeWindow); const start = end - getTimeDifferenceSeconds(selectedTimeWindow);
const start = new Date(end - timeDifferenceMinutes * msPerMinute).getTime();
return { start, end }; return { start, end };
}; };
... ...
......
...@@ -25,6 +25,18 @@ $item-weight-max-width: 48px; ...@@ -25,6 +25,18 @@ $item-weight-max-width: 48px;
flex-grow: 1; flex-grow: 1;
} }
.issue-token-state-icon-open {
color: $green-500;
}
.issue-token-state-icon-closed {
color: $blue-500;
}
.merge-request-status.closed {
color: $red-500;
}
.issue-token-state-icon-open, .issue-token-state-icon-open,
.issue-token-state-icon-closed, .issue-token-state-icon-closed,
.confidential-icon, .confidential-icon,
... ...
......
...@@ -193,7 +193,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -193,7 +193,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
return unless Feature.enabled?(:metrics_time_window, project) return unless Feature.enabled?(:metrics_time_window, project)
return unless params[:start].present? || params[:end].present? return unless params[:start].present? || params[:end].present?
params.require([:start, :end]).values_at(:start, :end) params.require([:start, :end])
end end
def search_environment_names def search_environment_names
... ...
......
...@@ -81,7 +81,7 @@ class ProjectsFinder < UnionFinder ...@@ -81,7 +81,7 @@ class ProjectsFinder < UnionFinder
if private_only? if private_only?
current_user.authorized_projects current_user.authorized_projects
else else
Project.public_or_visible_to_user(current_user, params[:visibility_level]) Project.public_or_visible_to_user(current_user)
end end
end end
end end
... ...
......
...@@ -459,41 +459,14 @@ class Project < ApplicationRecord ...@@ -459,41 +459,14 @@ class Project < ApplicationRecord
# Returns a collection of projects that is either public or visible to the # Returns a collection of projects that is either public or visible to the
# logged in user. # logged in user.
# def self.public_or_visible_to_user(user = nil)
# requested_visiblity_levels: Normally all projects that are visible if user
# to the user (e.g. internal and public) are queried, but this where('EXISTS (?) OR projects.visibility_level IN (?)',
# parameter allows the caller to narrow the search space to optimize user.authorizations_for_projects,
# database queries. For instance, a caller may only want to see Gitlab::VisibilityLevel.levels_for_user(user))
# internal projects. Instead of querying for internal and public
# projects and throwing away public projects, this parameter allows
# the query to be targeted for only internal projects.
def self.public_or_visible_to_user(user = nil, requested_visibility_levels = [])
return public_to_user unless user
visible_levels = Gitlab::VisibilityLevel.levels_for_user(user)
include_private = true
requested_visibility_levels = Array(requested_visibility_levels)
if requested_visibility_levels.present?
visible_levels &= requested_visibility_levels
include_private = requested_visibility_levels.include?(Gitlab::VisibilityLevel::PRIVATE)
end
public_or_internal_rel =
if visible_levels.present?
where('projects.visibility_level IN (?)', visible_levels)
else else
Project.none public_to_user
end end
private_rel =
if include_private
where('EXISTS (?)', user.authorizations_for_projects)
else
Project.none
end
public_or_internal_rel.or(private_rel)
end end
# project features may be "disabled", "internal", "enabled" or "public". If "internal", # project features may be "disabled", "internal", "enabled" or "public". If "internal",
... ...
......
...@@ -21,6 +21,7 @@ module MergeRequests ...@@ -21,6 +21,7 @@ module MergeRequests
post_merge_manually_merged post_merge_manually_merged
reload_merge_requests reload_merge_requests
outdate_suggestions outdate_suggestions
refresh_pipelines_on_merge_requests
reset_merge_when_pipeline_succeeds reset_merge_when_pipeline_succeeds
mark_pending_todos_done mark_pending_todos_done
cache_merge_requests_closing_issues cache_merge_requests_closing_issues
...@@ -107,8 +108,6 @@ module MergeRequests ...@@ -107,8 +108,6 @@ module MergeRequests
end end
merge_request.mark_as_unchecked merge_request.mark_as_unchecked
create_pipeline_for(merge_request, current_user)
UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
end end
# Upcoming method calls need the refreshed version of # Upcoming method calls need the refreshed version of
...@@ -134,6 +133,13 @@ module MergeRequests ...@@ -134,6 +133,13 @@ module MergeRequests
end end
end end
def refresh_pipelines_on_merge_requests
merge_requests_for_source_branch.each do |merge_request|
create_pipeline_for(merge_request, current_user)
UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
end
end
def reset_merge_when_pipeline_succeeds def reset_merge_when_pipeline_succeeds
merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds) merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds)
end end
... ...
......
---
title: Create pipelines for merge requests only when source branch is updated
merge_request: 26921
author:
type: fixed
---
title: Optimize /api/v4/projects endpoint for visibility level
merge_request: 26481
author:
type: performance
...@@ -62,7 +62,7 @@ into more features: ...@@ -62,7 +62,7 @@ into more features:
| [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. | | [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. |
| [Interactive web terminals](interactive_web_terminal/index.md) | Open an interactive web terminal to debug the running jobs. | | [Interactive web terminals](interactive_web_terminal/index.md) | Open an interactive web terminal to debug the running jobs. |
| [Review Apps](review_apps/index.md) | Configure GitLab CI/CD to preview code changes in a per-branch basis. | | [Review Apps](review_apps/index.md) | Configure GitLab CI/CD to preview code changes in a per-branch basis. |
| [Optimising GitLab for large repositories](large_repositories/index.md) | Useful tips on how to optimise GitLab and GitLab Runner for big repositories. | | [Optimizing GitLab for large repositories](large_repositories/index.md) | Useful tips on how to optimize GitLab and GitLab Runner for big repositories. |
| [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) **[PREMIUM]** | Check the current health and status of each CI/CD environment running on Kubernetes. | | [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) **[PREMIUM]** | Check the current health and status of each CI/CD environment running on Kubernetes. |
| [GitLab CI/CD for external repositories](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html) **[PREMIUM]** | Get the benefits of GitLab CI/CD combined with repositories in GitHub and BitBucket Cloud. | | [GitLab CI/CD for external repositories](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html) **[PREMIUM]** | Get the benefits of GitLab CI/CD combined with repositories in GitHub and BitBucket Cloud. |
... ...
......
# Optimising GitLab for large repositories # Optimizing GitLab for large repositories
Large repositories consisting of more than 50k files in a worktree Large repositories consisting of more than 50k files in a worktree
often require special consideration because of often require special consideration because of
the time required to clone and check out. the time required to clone and check out.
GitLab and GitLab Runner handle this scenario well GitLab and GitLab Runner handle this scenario well
but require optimised configuration to efficiently perform its but require optimized configuration to efficiently perform its
set of operations. set of operations.
The general guidelines for handling big repositories are simple. The general guidelines for handling big repositories are simple.
...@@ -15,7 +15,7 @@ Each guideline is described in more detail in the sections below: ...@@ -15,7 +15,7 @@ Each guideline is described in more detail in the sections below:
- Always use shallow clone to reduce data transfer. Be aware that this puts more burden - Always use shallow clone to reduce data transfer. Be aware that this puts more burden
on GitLab instance due to higher CPU impact. on GitLab instance due to higher CPU impact.
- Control the clone directory if you heavily use a fork-based workflow. - Control the clone directory if you heavily use a fork-based workflow.
- Optimise `git clean` flags to ensure that you remove or keep data that might affect or speed-up your build. - Optimize `git clean` flags to ensure that you remove or keep data that might affect or speed-up your build.
## Shallow cloning ## Shallow cloning
...@@ -76,7 +76,7 @@ done by GitLab, requiring you to do them. ...@@ -76,7 +76,7 @@ done by GitLab, requiring you to do them.
This can have implications if you heavily use big repositories with fork workflow. This can have implications if you heavily use big repositories with fork workflow.
Fork workflow from GitLab Runner's perspective is stored as a separate repository Fork workflow from GitLab Runner's perspective is stored as a separate repository
with separate worktree. That means that GitLab Runner cannot optimise the usage with separate worktree. That means that GitLab Runner cannot optimize the usage
of worktrees and you might have to instruct GitLab Runner to use that. of worktrees and you might have to instruct GitLab Runner to use that.
In such cases, ideally you want to make the GitLab Runner executor be used only used only In such cases, ideally you want to make the GitLab Runner executor be used only used only
...@@ -113,7 +113,7 @@ available parameters are dependent on Git version. ...@@ -113,7 +113,7 @@ available parameters are dependent on Git version.
Following the guidelines above, lets imagine that we want to: Following the guidelines above, lets imagine that we want to:
- Optimise for a big project (more than 50k files in directory). - Optimize for a big project (more than 50k files in directory).
- Use forks-based workflow for contributing. - Use forks-based workflow for contributing.
- Reuse existing worktrees. Have preconfigured runners that are pre-cloned with repositories. - Reuse existing worktrees. Have preconfigured runners that are pre-cloned with repositories.
- Runner assigned only to project and all forks. - Runner assigned only to project and all forks.
... ...
......
# frozen_string_literal: true # frozen_string_literal: true
module QA module QA
context 'Verify' do # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/91
context 'Verify', :quarantine do
describe 'CI variable support' do describe 'CI variable support' do
it 'user adds a CI variable' do it 'user adds a CI variable' do
Runtime::Browser.visit(:gitlab, Page::Main::Login) Runtime::Browser.visit(:gitlab, Page::Main::Login)
... ...
......
...@@ -419,6 +419,17 @@ describe Projects::EnvironmentsController do ...@@ -419,6 +419,17 @@ describe Projects::EnvironmentsController do
expect(json_response['data']).to eq({}) expect(json_response['data']).to eq({})
expect(json_response['last_update']).to eq(42) expect(json_response['last_update']).to eq(42)
end end
context 'when time params are provided' do
it 'returns a metrics JSON document' do
additional_metrics(start: '1554702993.5398998', end: '1554717396.996232')
expect(response).to be_ok
expect(json_response['success']).to be(true)
expect(json_response['data']).to eq({})
expect(json_response['last_update']).to eq(42)
end
end
end end
context 'when only one time param is provided' do context 'when only one time param is provided' do
... ...
......
import { getTimeDiff } from '~/monitoring/utils';
import { timeWindows } from '~/monitoring/constants';
describe('getTimeDiff', () => {
it('defaults to an 8 hour (28800s) difference', () => {
const params = getTimeDiff();
expect(params.end - params.start).toEqual(28800);
});
it('accepts time window as an argument', () => {
const params = getTimeDiff(timeWindows.thirtyMinutes);
expect(params.end - params.start).not.toEqual(28800);
});
it('returns a value for every defined time window', () => {
const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours');
nonDefaultWindows.forEach(window => {
const params = getTimeDiff(timeWindows[window]);
const diff = params.end - params.start;
// Ensure we're not returning the default, 28800 (the # of seconds in 8 hrs)
expect(diff).not.toEqual(28800);
expect(typeof diff).toEqual('number');
});
});
});
...@@ -2721,7 +2721,7 @@ describe Project do ...@@ -2721,7 +2721,7 @@ describe Project do
end end
describe '#any_lfs_file_locks?', :request_store do describe '#any_lfs_file_locks?', :request_store do
let!(:project) { create(:project) } set(:project) { create(:project) }
it 'returns false when there are no LFS file locks' do it 'returns false when there are no LFS file locks' do
expect(project.any_lfs_file_locks?).to be_falsey expect(project.any_lfs_file_locks?).to be_falsey
...@@ -3159,53 +3159,6 @@ describe Project do ...@@ -3159,53 +3159,6 @@ describe Project do
expect(projects).to eq([public_project]) expect(projects).to eq([public_project])
end end
end end
context 'with requested visibility levels' do
set(:internal_project) { create(:project, :internal, :repository) }
set(:private_project_2) { create(:project, :private) }
context 'with admin user' do
set(:admin) { create(:admin) }
it 'returns all projects' do
projects = described_class.all.public_or_visible_to_user(admin, [])
expect(projects).to match_array([public_project, private_project, private_project_2, internal_project])
end
it 'returns all public and private projects' do
projects = described_class.all.public_or_visible_to_user(admin, [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE])
expect(projects).to match_array([public_project, private_project, private_project_2])
end
it 'returns all private projects' do
projects = described_class.all.public_or_visible_to_user(admin, [Gitlab::VisibilityLevel::PRIVATE])
expect(projects).to match_array([private_project, private_project_2])
end
end
context 'with regular user' do
it 'returns authorized projects' do
projects = described_class.all.public_or_visible_to_user(user, [])
expect(projects).to match_array([public_project, private_project, internal_project])
end
it "returns user's public and private projects" do
projects = described_class.all.public_or_visible_to_user(user, [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE])
expect(projects).to match_array([public_project, private_project])
end
it 'returns one private project' do
projects = described_class.all.public_or_visible_to_user(user, [Gitlab::VisibilityLevel::PRIVATE])
expect(projects).to eq([private_project])
end
end
end
end end
describe '.with_feature_available_for_user' do describe '.with_feature_available_for_user' do
... ...
......
...@@ -146,7 +146,10 @@ describe MergeRequests::RefreshService do ...@@ -146,7 +146,10 @@ describe MergeRequests::RefreshService do
stub_ci_pipeline_yaml_file(YAML.dump(config)) stub_ci_pipeline_yaml_file(YAML.dump(config))
end end
subject { service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') } subject { service.new(project, @user).execute(@oldrev, @newrev, ref) }
let(:ref) { 'refs/heads/master' }
let(:project) { @project }
context "when .gitlab-ci.yml has merge_requests keywords" do context "when .gitlab-ci.yml has merge_requests keywords" do
let(:config) do let(:config) do
...@@ -162,14 +165,17 @@ describe MergeRequests::RefreshService do ...@@ -162,14 +165,17 @@ describe MergeRequests::RefreshService do
it 'create detached merge request pipeline with commits' do it 'create detached merge request pipeline with commits' do
expect { subject } expect { subject }
.to change { @merge_request.merge_request_pipelines.count }.by(1) .to change { @merge_request.merge_request_pipelines.count }.by(1)
.and change { @fork_merge_request.merge_request_pipelines.count }.by(1)
.and change { @another_merge_request.merge_request_pipelines.count }.by(0) .and change { @another_merge_request.merge_request_pipelines.count }.by(0)
expect(@merge_request.has_commits?).to be_truthy expect(@merge_request.has_commits?).to be_truthy
expect(@fork_merge_request.has_commits?).to be_truthy
expect(@another_merge_request.has_commits?).to be_falsy expect(@another_merge_request.has_commits?).to be_falsy
end end
it 'does not create detached merge request pipeline for forked project' do
expect { subject }
.not_to change { @fork_merge_request.merge_request_pipelines.count }
end
it 'create detached merge request pipeline for non-fork merge request' do it 'create detached merge request pipeline for non-fork merge request' do
subject subject
...@@ -177,12 +183,26 @@ describe MergeRequests::RefreshService do ...@@ -177,12 +183,26 @@ describe MergeRequests::RefreshService do
.to be_detached_merge_request_pipeline .to be_detached_merge_request_pipeline
end end
it 'create legacy detached merge request pipeline for fork merge request' do context 'when service is hooked by target branch' do
subject let(:ref) { 'refs/heads/feature' }
it 'does not create detached merge request pipeline' do
expect { subject }
.not_to change { @merge_request.merge_request_pipelines.count }
end
end
context 'when service runs on forked project' do
let(:project) { @fork_project }
it 'creates legacy detached merge request pipeline for fork merge request' do
expect { subject }
.to change { @fork_merge_request.merge_request_pipelines.count }.by(1)
expect(@fork_merge_request.merge_request_pipelines.first) expect(@fork_merge_request.merge_request_pipelines.first)
.to be_legacy_detached_merge_request_pipeline .to be_legacy_detached_merge_request_pipeline
end end
end
context 'when ci_use_merge_request_ref feature flag is false' do context 'when ci_use_merge_request_ref feature flag is false' do
before do before do
... ...
......