Commit c2447160 authored by Fatih Acet's avatar Fatih Acet

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ee into 7249-group-bulk-edit-issues-milestone

parents 2671cc5b a952cda5
......@@ -144,15 +144,6 @@ export default {
return {};
},
componentClassName() {
if (this.shouldRenderDiffs) {
if (!this.lastUpdatedAt && !this.discussion.resolved) {
return 'unresolved';
}
}
return '';
},
isExpanded() {
return this.discussion.expanded || this.alwaysExpanded;
},
......@@ -313,11 +304,11 @@ export default {
</script>
<template>
<timeline-entry-item class="note note-discussion" :class="componentClassName">
<timeline-entry-item class="note note-discussion">
<div class="timeline-content">
<div :data-discussion-id="discussion.id" class="discussion js-discussion-container">
<div v-if="shouldRenderDiffs" class="discussion-header note-wrapper">
<div v-once class="timeline-icon">
<div v-once class="timeline-icon align-self-start flex-shrink-0">
<user-avatar-link
v-if="author"
:link-href="author.path"
......@@ -326,7 +317,7 @@ export default {
:img-size="40"
/>
</div>
<div class="timeline-content">
<div class="timeline-content w-100">
<note-header
:author="author"
:created-at="firstNote.created_at"
......
......@@ -406,7 +406,7 @@ $note-form-margin-left: 72px;
border-radius: 0;
@media (min-width: map-get($grid-breakpoints, md)) {
top: 91px;
top: $mr-tabs-height + $header-height;
.with-performance-bar & {
top: 126px;
......@@ -598,7 +598,8 @@ $note-form-margin-left: 72px;
}
.discussion-header {
min-height: 74px;
min-height: $line-height-base * 2em;
box-sizing: content-box;
.note-header-info {
padding-bottom: 0;
......@@ -608,13 +609,10 @@ $note-form-margin-left: 72px;
overflow-x: auto;
overflow-y: hidden;
}
}
.unresolved {
.discussion-header {
.note-header-info {
margin-top: $gl-padding-8;
}
&.note-wrapper {
display: flex;
align-items: center;
}
}
......
......@@ -3,6 +3,8 @@
class RunnerJobsFinder
attr_reader :runner, :params
ALLOWED_INDEXED_COLUMNS = %w[id].freeze
def initialize(runner, params = {})
@runner = runner
@params = params
......@@ -11,7 +13,7 @@ class RunnerJobsFinder
def execute
items = @runner.builds
items = by_status(items)
items
sort_items(items)
end
private
......@@ -23,4 +25,19 @@ class RunnerJobsFinder
items.where(status: params[:status])
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def sort_items(items)
return items unless ALLOWED_INDEXED_COLUMNS.include?(params[:order_by])
order_by = params[:order_by]
sort = if /\A(ASC|DESC)\z/i.match?(params[:sort])
params[:sort]
else
:desc
end
items.order(order_by => sort)
end
# rubocop: enable CodeReuse/ActiveRecord
end
......@@ -46,7 +46,7 @@ class DroneCiService < CiService
end
def commit_status(sha, ref)
with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
with_reactive_cache(sha, ref) { |cached| cached[:commit_status] }
end
def calculate_reactive_cache(sha, ref)
......@@ -68,7 +68,7 @@ class DroneCiService < CiService
end
{ commit_status: status }
rescue Errno::ECONNREFUSED
rescue *Gitlab::HTTP::HTTP_ERRORS
{ commit_status: :error }
end
......
# frozen_string_literal: true
require 'sidekiq/api'
Sidekiq::Worker.extend ActiveSupport::Concern
module ApplicationWorker
......@@ -44,6 +46,10 @@ module ApplicationWorker
get_sidekiq_options['queue'].to_s
end
def queue_size
Sidekiq::Queue.new(queue).size
end
def bulk_perform_async(args_list)
Sidekiq::Client.push_bulk('class' => self, 'args' => args_list)
end
......
---
title: Removes EE differences for app/views/layouts/fullscreen.html.haml
merge_request:
author:
type: other
---
title: Add order_by and sort params to list runner jobs api
merge_request: 29629
author: Sujay Patel
type: added
---
title: Fix invalid SSL certificate errors on Drone CI service
merge_request: 30422
author:
type: fixed
---
title: Remove unresolved class and fixed height in discussion header
merge_request: 28440
author: David Palubin
type: other
# frozen_string_literal: true
class AddLastCiMinutesUsageNotificationLevelToNamespaces < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
add_column :namespaces, :last_ci_minutes_usage_notification_level, :integer
end
end
# frozen_string_literal: true
class RemoveGitalyFeatureFlags < ActiveRecord::Migration[5.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
FEATURES = %w[
gitaly_batch_lfs_pointers gitaly_blame gitaly_blob_get_all_lfs_pointers gitaly_blob_get_new_lfs_pointers
gitaly_branch_names gitaly_branch_names_contains_sha gitaly_branches gitaly_bundle_to_disk
gitaly_calculate_checksum gitaly_can_be_merged gitaly_cherry_pick gitaly_commit_count
gitaly_commit_deltas gitaly_commit_languages gitaly_commit_messages gitaly_commit_patch
gitaly_commit_raw_diffs gitaly_commit_stats gitaly_commit_tree_entry gitaly_commits_between
gitaly_commits_by_message gitaly_conflicts_list_conflict_files gitaly_conflicts_resolve_conflicts gitaly_count_commits
gitaly_count_diverging_commits_no_max gitaly_create_branch gitaly_create_repo_from_bundle gitaly_create_repository
gitaly_delete_branch gitaly_delete_refs gitaly_delta_islands gitaly_deny_disk_acces
gitaly_diff_between gitaly_extract_commit_signature gitaly_fetch_ref gitaly_fetch_remote
gitaly_fetch_source_branch gitaly_filter_shas_with_signature gitaly_filter_shas_with_signatures gitaly_find_all_commits
gitaly_find_branch gitaly_find_commit gitaly_find_commits gitaly_find_ref_name
gitaly_force_push gitaly_fork_repository gitaly_garbage_collect gitaly_get_info_attributes
gitaly_git_blob_load_all_data gitaly_git_blob_raw gitaly_git_fsck gitaly_go-find-all-tags
gitaly_has_local_branches gitaly_import_repository gitaly_is_ancestor gitaly_last_commit_for_path
gitaly_license_short_name gitaly_list_blobs_by_sha_path gitaly_list_commits_by_oid gitaly_local_branches
gitaly_ls_files gitaly_merge_base gitaly_merged_branch_names gitaly_new_commits
gitaly_operation_user_add_tag gitaly_operation_user_commit_file gitaly_operation_user_commit_files gitaly_operation_user_create_branch
gitaly_operation_user_delete_branch gitaly_operation_user_delete_tag gitaly_operation_user_ff_branch gitaly_operation_user_merge_branch
gitaly_post_receive_pack gitaly_post_upload_pack gitaly_project_raw_show gitaly_raw_changes_between
gitaly_rebase gitaly_rebase_in_progress gitaly_ref_delete_refs gitaly_ref_exists
gitaly_ref_exists_branch gitaly_ref_exists_branches gitaly_ref_find_all_remote_branches gitaly_remote_add_remote
gitaly_remote_fetch_internal_remote gitaly_remote_remove_remote gitaly_remote_update_remote_mirror gitaly_remove_namespace
gitaly_repack_full gitaly_repack_incremental gitaly_repository_cleanup gitaly_repository_exists
gitaly_repository_size gitaly_root_ref gitaly_search_files_by_content gitaly_search_files_by_name
gitaly_squash gitaly_squash_in_progress gitaly_ssh_receive_pack gitaly_ssh_upload_pack
gitaly_submodule_url_for gitaly_tag_messages gitaly_tag_names gitaly_tag_names_contains_sha
gitaly_tags gitaly_tree_entries gitaly_wiki_delete_page gitaly_wiki_find_file
gitaly_wiki_find_page gitaly_wiki_get_all_pages gitaly_wiki_page_formatted_data gitaly_wiki_page_versions
gitaly_wiki_update_page gitaly_wiki_write_page gitaly_workhorse_archive gitaly_workhorse_raw_show
gitaly_workhorse_send_git_diff gitaly_workhorse_send_git_patch gitaly_write_config gitaly_write_ref
]
class Feature < ActiveRecord::Base
self.table_name = 'features'
end
def up
Feature.where(key: FEATURES).delete_all
end
end
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20190628185004) do
ActiveRecord::Schema.define(version: 20190703130053) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -2116,6 +2116,7 @@ ActiveRecord::Schema.define(version: 20190628185004) do
t.boolean "auto_devops_enabled"
t.integer "extra_shared_runners_minutes_limit"
t.datetime_with_timezone "last_ci_minutes_notification_at"
t.integer "last_ci_minutes_usage_notification_level"
t.index ["created_at"], name: "index_namespaces_on_created_at", using: :btree
t.index ["custom_project_templates_group_id", "type"], name: "index_namespaces_on_custom_project_templates_group_id_and_type", where: "(custom_project_templates_group_id IS NOT NULL)", using: :btree
t.index ["file_template_project_id"], name: "index_namespaces_on_file_template_project_id", using: :btree
......
......@@ -441,12 +441,12 @@ and want to eliminate NFS from your environment altogether, there are
a few things that you need to do:
1. Make sure the [`git` user home directory](https://docs.gitlab.com/omnibus/settings/configuration.html#moving-the-home-directory-for-a-user) is on local disk.
1. Configure [database lookup of SSH keys](https://docs.gitlab.com/ce/administration/operations/fast_ssh_key_lookup.html)
1. Configure [database lookup of SSH keys](../operations/fast_ssh_key_lookup.md)
to eliminate the need for a shared authorized_keys file.
1. Configure [object storage for job artifacts](https://docs.gitlab.com/ce/administration/job_artifacts.html#using-object-storage)
including [live tracing](https://docs.gitlab.com/ce/administration/job_traces.html#new-live-trace-architecture).
1. Configure [object storage for LFS objects](https://docs.gitlab.com/ce/workflow/lfs/lfs_administration.html#storing-lfs-objects-in-remote-object-storage).
1. Configure [object storage for uploads](https://docs.gitlab.com/ce/administration/uploads.html#using-object-storage-core-only).
1. Configure [object storage for job artifacts](../job_artifacts.md#using-object-storage)
including [live tracing](../job_traces.md#new-live-trace-architecture).
1. Configure [object storage for LFS objects](../../workflow/lfs/lfs_administration.md#storing-lfs-objects-in-remote-object-storage).
1. Configure [object storage for uploads](../uploads.md#using-object-storage-core-only).
NOTE: **Note:** One current feature of GitLab still requires a shared directory (NFS): [GitLab Pages](../../user/project/pages/index.md).
There is [work in progress](https://gitlab.com/gitlab-org/gitlab-pages/issues/196)
......
......@@ -4,7 +4,7 @@ GitLab has an advanced log system where everything is logged so that you
can analyze your instance using various system log files. In addition to
system log files, GitLab Enterprise Edition comes with Audit Events.
Find more about them [in Audit Events
documentation](https://docs.gitlab.com/ee/administration/audit_events.html)
documentation](audit_events.md)
System log files are typically plain text in a standard log file format.
This guide talks about how to read and use these system log files.
......
......@@ -61,7 +61,7 @@ It will check that each component was set up according to the installation guide
You may also have a look at our Troubleshooting Guides:
- [Troubleshooting Guide (GitLab)](http://docs.gitlab.com/ee/README.html#troubleshooting)
- [Troubleshooting Guide (GitLab)](../index.md#troubleshooting)
- [Troubleshooting Guide (Omnibus Gitlab)](https://docs.gitlab.com/omnibus/README.html#troubleshooting)
**Omnibus Installation**
......
......@@ -291,6 +291,8 @@ GET /runners/:id/jobs
|-----------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a runner |
| `status` | string | no | Status of the job; one of: `running`, `success`, `failed`, `canceled` |
| `order_by`| string | no | Order jobs by `id`. |
| `sort` | string | no | Sort jobs in `asc` or `desc` order (default: `desc`) |
```
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/1/jobs?status=running"
......
......@@ -109,7 +109,7 @@ your repository:
new commits.
The web hook URL should be set to the GitLab API to
[trigger pull mirroring](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project-starter),
[trigger pull mirroring](../../api/projects.md#start-the-pull-mirroring-process-for-a-project-starter),
using the GitLab personal access token we just created.
```
......
......@@ -35,7 +35,7 @@ the `author` field. GitLab team members **should not**.
- Any user-facing change **should** have a changelog entry. Example: "GitLab now
uses system fonts for all text."
- Any change behind a feature flag **should not** have a changelog entry. The entry should be added [in the merge request removing the feature flags](https://docs.gitlab.com/ee/development/feature_flags.html#developing-with-feature-flags).
- Any change behind a feature flag **should not** have a changelog entry. The entry should be added [in the merge request removing the feature flags](feature_flags/development.md).
- A fix for a regression introduced and then fixed in the same release (i.e.,
fixing a bug introduced during a monthly release candidate) **should not**
have a changelog entry.
......
......@@ -17,6 +17,6 @@ To request access to Chatops on GitLab.com:
## See also
- [Chatops Usage](https://docs.gitlab.com/ee/ci/chatops/README.html)
- [Chatops Usage](../ci/chatops/README.md)
- [Understanding EXPLAIN plans](understanding_explain_plans.md)
- [Feature Groups](feature_flags/development.md#feature-groups)
......@@ -393,7 +393,7 @@ Instead:
Example:
```md
For more information, see the [confidential issue](https://docs.gitlab.com/ee/user/project/issues/confidential_issues.html) `https://gitlab.com/gitlab-org/gitlab-ce/issues/<issue_number>`.
For more information, see the [confidential issue](../../user/project/issues/confidential_issues.md) `https://gitlab.com/gitlab-org/gitlab-ce/issues/<issue_number>`.
```
### Unlinking emails
......
......@@ -5,7 +5,7 @@ GitLab Inc. provided environments such as staging and production, you need to
have access to the chatops bot. Chatops bot is currently running on the ops instance,
which is different from GitLab.com or dev.gitlab.org.
Follow the Chatops document to [request access](https://docs.gitlab.com/ee/development/chatops_on_gitlabcom.html#requesting-access).
Follow the Chatops document to [request access](../chatops_on_gitlabcom.md#requesting-access).
Once you are added to the project test if your access propagated,
run:
......@@ -112,7 +112,7 @@ instances. Make sure to add the ~"feature flag" label to this merge request so
release managers are aware the changes are hidden behind a feature flag. If the
merge request has to be picked into a stable branch, make sure to also add the
appropriate "Pick into X" label (e.g. "Pick into XX.X").
See [the process document](https://docs.gitlab.com/ee/development/feature_flags/process.html#including-a-feature-behind-feature-flag-in-the-final-release) for further details.
See [the process document](process.md#including-a-feature-behind-feature-flag-in-the-final-release) for further details.
When a feature gate has been removed from the code base, the value still exists
in the database.
......
......@@ -57,7 +57,7 @@ the feature flag check will default to `true`.
As an example, if you were to ship the backend half of a feature behind a flag,
you'd want to explicitly disable that flag until the frontend half is also ready
to be shipped. [You can do this via Chatops](https://docs.gitlab.com/ee/development/feature_flags/controls.html):
to be shipped. [You can do this via Chatops](controls.md):
```
/chatops run feature set some_feature 0
......
......@@ -394,15 +394,15 @@ end
By defining the `api_get_path` method, we allow the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to get a single issue.
> This `GET` path can be found in the [public API documentation](https://docs.gitlab.com/ee/api/issues.html#single-issue).
> This `GET` path can be found in the [public API documentation](../../../api/issues.md#single-issue).
By defining the `api_post_path` method, we allow the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to create a new issue in a specific project.
> This `POST` path can be found in the [public API documentation](https://docs.gitlab.com/ee/api/issues.html#new-issue).
> This `POST` path can be found in the [public API documentation](../../../api/issues.md#new-issue).
By defining the `api_post_body` method, we allow the [`ApiFabricator.api_post`](https://gitlab.com/gitlab-org/gitlab-ee/blob/a9177ca1812bac57e2b2fa4560e1d5dd8ffac38b/qa/qa/resource/api_fabricator.rb#L68) method to know which data to send when making the `POST` request.
> Notice that we pass both `labels` and `title` attributes in the `api_post_body`, where `labels` receives an array of labels, and [`title` is required](https://docs.gitlab.com/ee/api/issues.html#new-issue). Also, notice that we keep them alphabetically organized.
> Notice that we pass both `labels` and `title` attributes in the `api_post_body`, where `labels` receives an array of labels, and [`title` is required](../../../api/issues.md#new-issue). Also, notice that we keep them alphabetically organized.
**Label resource**
......@@ -441,7 +441,7 @@ By defining the `api_post_path` method, we allow for the [`ApiFabricator `](http
By defining the `api_post_body` method, we we allow for the [`ApiFabricator.api_post`](https://gitlab.com/gitlab-org/gitlab-ee/blob/a9177ca1812bac57e2b2fa4560e1d5dd8ffac38b/qa/qa/resource/api_fabricator.rb#L68) method to know which data to send when making the `POST` request.
> Notice that we pass both `color` and `name` attributes in the `api_post_body` since [those are required](https://docs.gitlab.com/ee/api/labels.html#create-a-new-label). Also, notice that we keep them alphabetically organized.
> Notice that we pass both `color` and `name` attributes in the `api_post_body` since [those are required](../../../api/labels.md#create-a-new-label). Also, notice that we keep them alphabetically organized.
### 8. Page Objects
......
......@@ -128,9 +128,9 @@ GitLab can be configured to authenticate with other OAuth providers, LDAP, SAML,
Kerberos, etc. Here are some documents you might be interested in reading:
- [Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/)
- [Integration documentation](https://docs.gitlab.com/ce/integration/)
- [GitLab Pages configuration](https://docs.gitlab.com/ce/administration/pages/index.html)
- [GitLab Container Registry configuration](https://docs.gitlab.com/ce/administration/container_registry.html)
- [Integration documentation](../../integration/README.md)
- [GitLab Pages configuration](../../administration/pages/index.md)
- [GitLab Container Registry configuration](../../administration/container_registry.md)
[freetrial]: https://console.cloud.google.com/freetrial "GCP free trial"
[ip]: https://cloud.google.com/compute/docs/configure-instance-ip-addresses#promote_ephemeral_ip "Configuring an Instance's IP Addresses"
......
......@@ -167,7 +167,7 @@ cd pcre2-10.33
chmod +x configure
./configure --prefix=/usr --enable-jit
make
make install
sudo make install
# Download and compile from source
cd /tmp
......@@ -634,8 +634,8 @@ Gitaly must be running for the next section.
gitlab_path=/home/git/gitlab
gitaly_path=/home/git/gitaly
sudo -u git -H $gitlab_path/bin/daemon_with_pidfile $gitlab_path/tmp/pids/gitaly.pid \
$gitaly_path/gitaly $gitaly_path/config.toml >> $gitlab_path/log/gitaly.log 2>&1 &
sudo -u git -H sh -c "$gitlab_path/bin/daemon_with_pidfile $gitlab_path/tmp/pids/gitaly.pid \
$gitaly_path/gitaly $gitaly_path/config.toml >> $gitlab_path/log/gitaly.log 2>&1 &"
```
### Initialize Database and Activate Advanced Features
......
......@@ -13,6 +13,6 @@ comments: false
- [User management](user_management.md)
- [Webhooks](web_hooks.md)
- [Import](import.md) of git repositories in bulk
- [Rebuild authorized_keys file](http://docs.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators
- [Rebuild authorized_keys file](../administration/raketasks/maintenance.md#rebuild-authorized_keys-file) task for administrators
- [Migrate Uploads](../administration/raketasks/uploads/migrate.md)
- [Sanitize Uploads](../administration/raketasks/uploads/sanitize.md)
......@@ -35,9 +35,9 @@ with various cloud providers.
### Build, test, deploy
In order to provide modern DevOps workflows, our Application Development Platform will rely on
[Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/) to provide those workflows. Auto DevOps works with
[Auto DevOps](../autodevops/index.md) to provide those workflows. Auto DevOps works with
any Kubernetes cluster; you're not limited to running on GitLab's infrastructure. Additionally, Auto DevOps offers
an incremental consumption path. Because it is [composable](https://docs.gitlab.com/ee/topics/autodevops/#using-components-of-auto-devops),
an incremental consumption path. Because it is [composable](../autodevops/index.md#using-components-of-auto-devops),
you can use as much or as little of the default pipeline as you'd like, and deeply customize without having to integrate a completely different platform.
### Security
......@@ -47,10 +47,10 @@ that may lead to security problems and unintended use. This can be achieved by m
which inform security teams and developers if there is something to consider changing in their apps
before it is too late to create a preventative fix. The following features are included:
- [Auto SAST (Static Application Security Testing)](https://docs.gitlab.com/ee/topics/autodevops/#auto-sast-ultimate)
- [Auto Dependency Scanning](https://docs.gitlab.com/ee/topics/autodevops/#auto-dependency-scanning-ultimate)
- [Auto Container Scanning](https://docs.gitlab.com/ee/topics/autodevops/#auto-container-scanning-ultimate)
- [Auto DAST (Dynamic Application Security Testing)](https://docs.gitlab.com/ee/topics/autodevops/#auto-dast-ultimate)
- [Auto SAST (Static Application Security Testing)](../autodevops/index.md#auto-sast-ultimate)
- [Auto Dependency Scanning](../autodevops/index.md#auto-dependency-scanning-ultimate)
- [Auto Container Scanning](../autodevops/index.md#auto-container-scanning-ultimate)
- [Auto DAST (Dynamic Application Security Testing)](../autodevops/index.md#auto-dast-ultimate)
### Observability
......@@ -58,5 +58,5 @@ Performance is a critical aspect of the user experience, and ensuring your appli
responsibility. The Application Development Platform integrates key performance analytics and feedback
into GitLab, automatically. The following features are included:
- [Auto Monitoring](https://docs.gitlab.com/ee/topics/autodevops/#auto-monitoring)
- [In-app Kubernetes Pod Logs](https://docs.gitlab.com/ee/user/project/clusters/kubernetes_pod_logs.html)
\ No newline at end of file
- [Auto Monitoring](../autodevops/index.md#auto-monitoring)
- [In-app Kubernetes Pod Logs](../../user/project/clusters/kubernetes_pod_logs.md)
......@@ -73,7 +73,7 @@ The GitLab University curriculum is composed of GitLab videos, screencasts, pres
- Being part of our Great Community and Contributing to GitLab
1. [Getting Started with the GitLab Development Kit (GDK)](https://about.gitlab.com/2016/06/08/getting-started-with-gitlab-development-kit/)
1. [Contributing Technical Articles to the GitLab Blog](https://about.gitlab.com/2016/01/26/call-for-writers/)
1. [GitLab Training Workshops](https://docs.gitlab.com/ce/university/training/end-user/)
1. [GitLab Training Workshops](training/end-user/README.md)
1. [GitLab Professional Services](https://about.gitlab.com/services/)
### 1.8 GitLab Training Material
......
......@@ -46,12 +46,19 @@ The following languages and package managers are supported.
| Language | Package managers | Scan Tool |
|------------|-------------------------------------------------------------------|----------------------------------------------------------|
| JavaScript | [Bower](https://bower.io/), [npm](https://www.npmjs.com/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Go | [Godep](https://github.com/tools/godep), go get |[License Finder](https://github.com/pivotal/LicenseFinder)|
| JavaScript | [Bower](https://bower.io/), [npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Go | [Godep](https://github.com/tools/godep), go get ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), gvt ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), glide ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), dep ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), trash ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) and govendor ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), [go mod](https://github.com/golang/go/wiki/Modules) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Java | [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| .NET | [Nuget](https://www.nuget.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Python | [pip](https://pip.pypa.io/en/stable/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Ruby | [gem](https://rubygems.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Erlang | [rebar](https://www.rebar3.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) , [CocoaPods v0.39 and below](https://cocoapods.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Elixir | [mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| C++/C | [conan](https://conan.io/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
| Scala | [sbt](https://www.scala-sbt.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
| Rust | [cargo](https://crates.io/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
| PHP | [composer](https://getcomposer.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
## Requirements
......
......@@ -353,12 +353,10 @@ High Performance TCP/HTTP Load Balancer:
[4010]: https://gitlab.com/gitlab-com/infrastructure/issues/4010 "Find a good value for maximum timeout for Shared Runners"
[4070]: https://gitlab.com/gitlab-com/infrastructure/issues/4070 "Configure per-runner timeout for shared-runners-manager-X on GitLab.com"
## Other admin area settings
## Group and project settings
This area highlights other noteworthy admin area settings on GitLab.com that differ from default settings. This list is not exhaustive.
On GitLab.com, projects, groups, and snippets created
after July 2019 have the `Internal` visibility setting disabled.
NOTE: **Note:**
From July 2019, the `Internal` visibility setting is disabled for new projects, groups,
and snippets on GitLab.com. Existing projects, groups, and snippets using the `Internal`
visibility setting keep this setting. You can read more about the change in the
You can read more about the change in the
[relevant issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/12388).
......@@ -24,7 +24,7 @@ to the webhook URL.
In most cases, you'll need to set up your own [webhook receiver](#example-webhook-receiver)
to receive information from GitLab, and send it to another app, according to your needs.
We already have a [built-in receiver](https://docs.gitlab.com/ce/project_services/slack.html)
We already have a [built-in receiver](slack.md)
for sending [Slack](https://api.slack.com/incoming-webhooks) notifications _per project_.
## Overview
......
......@@ -115,7 +115,7 @@ The "Move issue" button is at the bottom of the right-sidebar when viewing the i
If you have advanced technical skills you can also bulk move all the issues from one project to another in the rails console. The below script will move all the issues from one project to another that are not in status **closed**.
To access rails console run `sudo gitlab-rails console` on the GitLab server and run the below script. Please be sure to change **project**, **admin_user** and **target_project** to your values. We do also recommend [creating a backup](https://docs.gitlab.com/ee/raketasks/backup_restore.html#creating-a-backup-of-the-gitlab-system) before attempting any changes in the console.
To access rails console run `sudo gitlab-rails console` on the GitLab server and run the below script. Please be sure to change **project**, **admin_user** and **target_project** to your values. We do also recommend [creating a backup](../../../raketasks/backup_restore.md#creating-a-backup-of-the-gitlab-system) before attempting any changes in the console.
```ruby
project = Project.find_by_full_path('full path of the project where issues are moved from')
......
......@@ -380,7 +380,7 @@ What you can do with GitLab CI is pretty much up to your
creativity. Once you get used to it, you start creating
awesome scripts that automate most of tasks you'd do
manually in the past. Read through the
[documentation of GitLab CI](https://docs.gitlab.com/ce/ci/yaml/README.html)
[documentation of GitLab CI](../../../ci/yaml/README.md)
to understand how to go even further on your scripts.
- On this blog post, understand the concept of
......
......@@ -187,10 +187,13 @@ information in the UI.
DANGER: **Warning:**
This is a destructive action that leads to data loss. Use with caution.
If you are either the owner of a given job or have Master
[permissions](../../permissions.md#gitlab-cicd-permissions)
on the project, you can erase a single job via the UI which will also remove the
artifacts and the job's trace.
You can erase a single job via the UI, which will also remove the job's
artifacts and trace, if you are:
- The owner of the job.
- A [Maintainer](../../permissions.md#gitlab-cicd-permissions) of the project.
To erase a job:
1. Navigate to a job's page.
1. Click the trash icon at the top right of the job's trace.
......
......@@ -286,10 +286,10 @@ project mirroring again by [Forcing an update](#forcing-an-update-core).
[GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
Pull mirroring uses polling to detect new branches and commits added upstream, often minutes
afterwards. If you notify GitLab by [API](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project-starter),
afterwards. If you notify GitLab by [API](../api/projects.md#start-the-pull-mirroring-process-for-a-project-starter),
updates will be pulled immediately.
For more information, see [Start the pull mirroring process for a Project](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project-starter).
For more information, see [Start the pull mirroring process for a Project](../api/projects.md#start-the-pull-mirroring-process-for-a-project-starter).
## Forcing an update **[CORE]**
......
......@@ -94,6 +94,7 @@ export default {
'clearSearchResults',
'toggleSelectedProject',
'setSearchQuery',
'removeProject',
]),
addProjects() {
this.addProjectsToDashboard();
......@@ -153,7 +154,7 @@ export default {
<div class="prepend-top-default">
<div v-if="projects.length" class="dashboard-cards">
<div v-for="project in projects" :key="project.id" class="column prepend-top-default">
<project-header :project="project" />
<project-header :project="project" @remove="removeProject" />
<div class="row">
<environment
v-for="environment in project.environments"
......
# frozen_string_literal: true
module EE
module RunnersHelper
def purchase_shared_runner_minutes_link(user, project)
if ::Gitlab.com? && can?(user, :admin_project, project)
link_to(_("Click here"), EE::SUBSCRIPTIONS_PLANS_URL, target: '_blank', rel: 'noopener') + s_("Pipelines| to purchase more minutes.")
def ci_usage_warning_message(namespace, project)
message = ci_usage_base_message(namespace)
return unless message
if ::Gitlab.com? && can?(current_user, :admin_project, project)
message += " #{purchase_shared_runner_minutes_link}"
elsif namespace.shared_runners_minutes_used?
message += s_('Pipelines|Pipelines will not run anymore on shared Runners.')
end
message.html_safe
end
def ci_usage_warning_class(namespace)
if EE::Namespace::CI_USAGE_ALERT_LEVELS.min == namespace.last_ci_minutes_usage_notification_level
'alert-danger'
else
s_("Pipelines|Pipelines will not run anymore on shared Runners.")
'alert-warning'
end
end
private
def purchase_shared_runner_minutes_link
link = link_to(_("Click here"), EE::SUBSCRIPTIONS_PLANS_URL, target: '_blank', rel: 'noopener')
link + s_("Pipelines| to purchase more minutes.")
end
def ci_usage_base_message(namespace)
if namespace.shared_runners_minutes_used?
s_("Pipelines|%{namespace_name} has exceeded its pipeline minutes quota.") % { namespace_name: namespace.name }
elsif namespace.last_ci_minutes_usage_notification_level
s_("Pipelines|%{namespace_name} has less than %{notification_level}%% of CI minutes available.") % { namespace_name: namespace.name, notification_level: namespace.last_ci_minutes_usage_notification_level }
end
end
end
......
# frozen_string_literal: true
class CiMinutesUsageMailer < BaseMailer
def notify(namespace_name, contact_email)
def notify(namespace_name, recipients)
@namespace_name = namespace_name
mail(
to: contact_email,
bcc: recipients,
subject: "GitLab CI Runner Minutes quota for #{namespace_name} has run out"
)
end
def notify_limit(namespace_name, recipients, percentage_of_available_mins)
@namespace_name = namespace_name
@percentage_of_available_mins = percentage_of_available_mins
mail(
bcc: recipients,
subject: "GitLab CI Runner Minutes quota for #{namespace_name} has \
less than #{percentage_of_available_mins}% available"
)
end
end
......@@ -27,6 +27,8 @@ module EE
LICENSE_PLANS_TO_NAMESPACE_PLANS = NAMESPACE_PLANS_TO_LICENSE_PLANS.invert.freeze
PLANS = NAMESPACE_PLANS_TO_LICENSE_PLANS.keys.freeze
CI_USAGE_ALERT_LEVELS = [30, 5].freeze
prepended do
include EachBatch
......
......@@ -150,7 +150,7 @@ module EE
def force_import_job!
return if mirror_update_due? || updating_mirror?
set_next_execution_to_now
set_next_execution_to_now(prioritized: true)
reset_retry_count if hard_failed?
save!
......@@ -158,10 +158,10 @@ module EE
UpdateAllMirrorsWorker.perform_async
end
def set_next_execution_to_now
def set_next_execution_to_now(prioritized: false)
return unless mirror?
self.next_execution_timestamp = Time.now
self.next_execution_timestamp = prioritized ? 5.minutes.ago : Time.now
end
def retry_limit_exceeded?
......
......@@ -2,22 +2,53 @@
class CiMinutesUsageNotifyService < BaseService
def execute
notify_on_total_usage
notify_on_partial_usage
end
private
def recipients
namespace.user? ? [namespace.owner.email] : namespace.owners.pluck(:email) # rubocop:disable CodeReuse/ActiveRecord
end
def notify_on_total_usage
return unless namespace.shared_runners_minutes_used? && namespace.last_ci_minutes_notification_at.nil?
namespace.update_columns(last_ci_minutes_notification_at: Time.now)
owners.each do |user|
CiMinutesUsageMailer.notify(namespace.name, user.email).deliver_later
end
CiMinutesUsageMailer.notify(namespace.name, recipients).deliver_later
end
private
def notify_on_partial_usage
return if namespace.shared_runners_minutes_used?
return if namespace.last_ci_minutes_usage_notification_level == current_alert_level
return if alert_levels.max < ci_minutes_percent_left
namespace.update_columns(last_ci_minutes_usage_notification_level: current_alert_level)
CiMinutesUsageMailer.notify_limit(namespace.name, recipients, current_alert_level).deliver_later
end
def namespace
@namespace ||= project.shared_runners_limit_namespace
end
def owners
namespace.user? ? [namespace.owner] : namespace.owners
def ci_minutes_percent_left
quota = namespace.actual_shared_runners_minutes_limit
used = namespace.shared_runners_minutes.to_i
minutes_left = quota - used
return 0 if minutes_left <= 0
(minutes_left.to_f * 100) / quota.to_f
end
def alert_levels
@alert_levels ||= EE::Namespace::CI_USAGE_ALERT_LEVELS.sort
end
def current_alert_level
@current_alert_level ||= alert_levels.find { |level| level >= ci_minutes_percent_left }
end
end
%p
This is an automated notification to let you know that your CI Runner Minutes quota for "#{@namespace_name}" is below #{@percentage_of_available_mins}%.
%p
Click #{link_to('here', EE::SUBSCRIPTIONS_PLANS_URL)} to purchase more minutes.
%p
If you need assistance, please contact #{link_to('GitLab support', 'https://support.gitlab.com')}.
This is an automated notification to let you know that your CI Runner Minutes
quota for "<%= @namespace_name %>" is below <%= @percentage_of_available_mins %>%.
Please visit <%= EE::SUBSCRIPTIONS_PLANS_URL %> to purchase more minutes.
If you need assistance, please contact GitLab support (https://support.gitlab.com).
......@@ -3,11 +3,11 @@
- scope = (project || namespace).full_path
- has_limit = (project || namespace).shared_runners_minutes_limit_enabled?
- can_see_status = project.nil? || can?(current_user, :create_pipeline, project)
- ci_warning_message = ci_usage_warning_message(namespace, project)
- if cookies[:hide_shared_runner_quota_message].blank? && has_limit && namespace.shared_runners_minutes_used? && can_see_status
.shared-runner-quota-message.alert.alert-warning.d-none.d-sm-block{ data: { scope: scope } }
= namespace.name
has exceeded its pipeline minutes quota. #{purchase_shared_runner_minutes_link(current_user, project)}
- if cookies[:hide_shared_runner_quota_message].blank? && has_limit && can_see_status && ci_warning_message.present?
.shared-runner-quota-message.alert.d-none.d-sm-block{ class: ci_usage_warning_class(namespace), data: { scope: scope } }
= ci_warning_message
.float-right
= link_to 'Remind later', '#', class: 'hide-shared-runner-limit-message alert-link'
......@@ -19,8 +19,9 @@ class ClearSharedRunnersMinutesWorker
.update_all("extra_shared_runners_minutes_limit = #{extra_minutes_left_sql} FROM namespace_statistics")
end
Namespace.where.not(last_ci_minutes_notification_at: nil).each_batch do |relation|
relation.update_all(last_ci_minutes_notification_at: nil)
Namespace.where('last_ci_minutes_notification_at IS NOT NULL OR last_ci_minutes_usage_notification_level IS NOT NULL')
.each_batch do |relation|
relation.update_all(last_ci_minutes_notification_at: nil, last_ci_minutes_usage_notification_level: nil)
end
NamespaceStatistics.where.not(shared_runners_seconds: 0)
......
......@@ -7,17 +7,18 @@ class UpdateAllMirrorsWorker
LEASE_TIMEOUT = 5.minutes
SCHEDULE_WAIT_TIMEOUT = 4.minutes
LEASE_KEY = 'update_all_mirrors'.freeze
RESCHEDULE_WAIT = 10.seconds
RESCHEDULE_WAIT = 1.second
def perform
return if Gitlab::Database.read_only?
scheduling_ran = with_lease do
schedule_mirrors!
scheduled = 0
with_lease do
scheduled = schedule_mirrors!
end
# If we didn't get the lease, exit early
return unless scheduling_ran
# If we didn't get the lease, or no updates were scheduled, exit early
return unless scheduled > 0
# Wait to give some jobs a chance to complete
Kernel.sleep(RESCHEDULE_WAIT)
......@@ -26,7 +27,7 @@ class UpdateAllMirrorsWorker
# reschedule this job to enqueue more work.
#
# This is in addition to the regular (cron-like) scheduling of this job.
reschedule_if_capacity_left
UpdateAllMirrorsWorker.perform_async if Gitlab::Mirror.reschedule_immediately?
end
# rubocop: disable CodeReuse/ActiveRecord
......@@ -37,7 +38,7 @@ class UpdateAllMirrorsWorker
# can't end up in an infinite loop
now = Time.now
last = nil
all_project_ids = []
scheduled = 0
while capacity > 0
batch_size = [capacity * 2, 500].min
......@@ -47,7 +48,8 @@ class UpdateAllMirrorsWorker
project_ids = projects.lazy.select(&:mirror?).take(capacity).map(&:id).force
capacity -= project_ids.length
all_project_ids.concat(project_ids)
ProjectImportScheduleWorker.bulk_perform_async(project_ids.map { |id| [id] })
scheduled += project_ids.length
# If fewer than `batch_size` projects were returned, we don't need to query again
break if projects.length < batch_size
......@@ -55,18 +57,18 @@ class UpdateAllMirrorsWorker
last = projects.last.import_state.next_execution_timestamp
end
ProjectImportScheduleWorker.bulk_perform_and_wait(all_project_ids.map { |id| [id] }, timeout: SCHEDULE_WAIT_TIMEOUT.to_i)
if scheduled > 0
# Wait for all ProjectImportScheduleWorker jobs to complete
deadline = Time.now + SCHEDULE_WAIT_TIMEOUT
sleep 1 while ProjectImportScheduleWorker.queue_size > 0 && Time.now < deadline
end
scheduled
end
# rubocop: enable CodeReuse/ActiveRecord
private
def reschedule_if_capacity_left
return unless Gitlab::Mirror.reschedule_immediately?
UpdateAllMirrorsWorker.perform_async
end
def with_lease
if lease_uuid = try_obtain_lease
yield
......
---
title: Add notifications for CI Minutes quota limit approaching
merge_request: 14328
author:
type: added
---
title: Prioritize mirrors for CI over other mirrors
merge_request: 14575
author:
type: changed
---
title: Increase rate at which UpdateAllMirrorsWorker schedules jobs and reschedules itself
merge_request: 14573
author:
type: other
---
title: Add Ability to Remove Projects From the Envivonment Dashboard
merge_request: 14563
author:
type: feature
......@@ -25,6 +25,7 @@ module EE
# Reset last_ci_minutes_notification_at if customer purchased extra CI minutes.
if params[:extra_shared_runners_minutes_limit].present?
update_attrs[:last_ci_minutes_notification_at] = nil
update_attrs[:last_ci_minutes_usage_notification_level] = nil
end
namespace.update(update_attrs)
......
......@@ -33,15 +33,7 @@ module Gitlab
end
def reschedule_immediately?
available_spots = available_capacity
return false if available_spots < capacity_threshold
# Only reschedule if we are able to completely fill up the available spots.
mirrors_ready_to_sync_count(available_spots) >= available_spots
end
def mirrors_ready_to_sync_count(up_to = nil)
Project.mirrors_to_sync(Time.now, limit: up_to).count
available_capacity >= capacity_threshold
end
def available_capacity
......
......@@ -27,6 +27,24 @@ describe 'CI shared runner limits' do
end
context 'when limit is defined' do
before do
stub_const("EE::Namespace::CI_USAGE_ALERT_LEVELS", [30, 5])
end
context 'when usage has reached a notification level' do
let(:group) { create(:group, :with_build_minutes_limit, last_ci_minutes_usage_notification_level: 30) }
it 'displays a warning message on pipelines page' do
visit_project_pipelines
expect_quota_exceeded_alert("#{group.name} has less than 30% of CI minutes available.")
end
it 'displays a warning message on project homepage' do
visit_project_home
expect_quota_exceeded_alert("#{group.name} has less than 30% of CI minutes available.")
end
end
context 'when limit is exceeded' do
let(:group) { create(:group, :with_used_build_minutes_limit) }
......
......@@ -26,6 +26,7 @@ describe('dashboard', () => {
clearSearchResults: jest.fn(),
setSearchQuery: jest.fn(),
fetchSearchResults: jest.fn(),
removeProject: jest.fn(),
toggleSelectedProject: jest.fn(),
};
propsData = {
......@@ -89,6 +90,12 @@ describe('dashboard', () => {
const headers = wrapper.findAll(ProjectHeader);
expect(headers.length).toBe(2);
});
it('should remove a project if it emits `remove`', () => {
const header = wrapper.find(ProjectHeader);
header.vm.$emit('remove');
expect(actionSpies.removeProject).toHaveBeenCalled();
});
});
describe('environment component', () => {
......
......@@ -2,6 +2,7 @@
exports[`Card security reports app Empty State renders correctly renders empty state component with correct props 1`] = `
Object {
"compact": false,
"description": "The security dashboard displays the latest security report. Use it to find and fix vulnerabilities.",
"primaryButtonLink": "http://test.host/help_dashboard",
"primaryButtonText": "Learn more",
......
# frozen_string_literal: true
require "spec_helper"
describe EE::RunnersHelper do
describe '.ci_usage_warning_message' do
let(:project) { create(:project, namespace: namespace) }
let(:minutes_used) { 0 }
let(:user) { create(:user) }
let(:namespace) do
create(:group, shared_runners_minutes_limit: 100)
end
let!(:statistics) do
create(:namespace_statistics, namespace: namespace, shared_runners_seconds: minutes_used * 60)
end
before do
allow(::Gitlab).to receive(:com?).and_return(true)
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).with(user, :admin_project, project) { false }
stub_const("EE::Namespace::CI_USAGE_ALERT_LEVELS", [50])
end
subject { helper.ci_usage_warning_message(namespace, project) }
context 'when CI minutes quota is above the warning limits' do
let(:minutes_used) { 40 }
it 'does not return a message' do
expect(subject).to be_nil
end
end
context 'when current user is an owner' do
before do
allow(helper).to receive(:can?).with(user, :admin_project, project) { true }
end
context 'when usage has reached first level of notification' do
before do
namespace.update_attribute(:last_ci_minutes_usage_notification_level, 50)
end
it 'shows the partial usage message' do
expect(subject).to match("#{namespace.name} has less than 50% of CI minutes available.")
expect(subject).to match('to purchase more minutes')
end
end
context 'when usage is above the quota' do
let(:minutes_used) { 120 }
it 'shows the total usage message' do
expect(subject).to match("#{namespace.name} has exceeded its pipeline minutes quota.")
expect(subject).to match('to purchase more minutes')
end
end
end
context 'when current user is not an owner' do
context 'when usage has reached first level of notification' do
before do
namespace.update_attribute(:last_ci_minutes_usage_notification_level, 50)
end
it 'shows the partial usage message without the purchase link' do
expect(subject).to match("#{namespace.name} has less than 50% of CI minutes available.")
expect(subject).not_to match('to purchase more minutes')
end
end
context 'when usage is above the quota' do
let(:minutes_used) { 120 }
it 'shows the total usage message without the purchase link' do
expect(subject).to match("#{namespace.name} has exceeded its pipeline minutes quota.")
expect(subject).not_to match('to purchase more minutes')
end
end
end
end
end
......@@ -66,60 +66,24 @@ describe Gitlab::Mirror do
describe '#reschedule_immediately?' do
let(:mirror_capacity_threshold) { Gitlab::CurrentSettings.mirror_capacity_threshold }
context 'with number of mirrors to sync equal to the available capacity' do
it 'returns true if available capacity surpassed defined threshold' do
available_capacity = mirror_capacity_threshold + 1
expect(described_class).to receive(:available_capacity).and_return(available_capacity)
expect(described_class).to receive(:mirrors_ready_to_sync_count).and_return(available_capacity)
expect(described_class.reschedule_immediately?).to eq(true)
end
it 'returns true if available capacity is equal to the defined threshold' do
expect(described_class).to receive(:available_capacity).and_return(mirror_capacity_threshold)
expect(described_class).to receive(:mirrors_ready_to_sync_count).and_return(mirror_capacity_threshold)
expect(described_class.reschedule_immediately?).to eq(true)
end
end
context 'with number of mirrors to sync surpassing the available capacity' do
it 'returns true if available capacity surpassed defined threshold' do
available_capacity = mirror_capacity_threshold + 1
expect(described_class).to receive(:available_capacity).and_return(available_capacity)
expect(described_class).to receive(:mirrors_ready_to_sync_count).and_return(available_capacity + 1)
expect(described_class.reschedule_immediately?).to eq(true)
context 'when available capacity exceeds the defined threshold' do
before do
expect(described_class).to receive(:available_capacity).and_return(mirror_capacity_threshold + 1)
end
it 'returns true if available capacity is equal to the defined threshold' do
expect(described_class).to receive(:available_capacity).and_return(mirror_capacity_threshold)
expect(described_class).to receive(:mirrors_ready_to_sync_count).and_return(mirror_capacity_threshold + 1)
expect(described_class.reschedule_immediately?).to eq(true)
it 'returns true' do
expect(described_class.reschedule_immediately?).to be_truthy
end
end
it 'returns false if mirrors ready to sync is below the available capacity' do
expect(described_class).to receive(:available_capacity).and_return(mirror_capacity_threshold + 1)
expect(described_class).to receive(:mirrors_ready_to_sync_count).and_return(mirror_capacity_threshold)
expect(described_class.reschedule_immediately?).to eq(false)
context 'when the availabile capacity is lower than the defined threshold' do
before do
expect(described_class).to receive(:available_capacity).and_return(mirror_capacity_threshold - 1)
end
it 'returns false if available capacity is below the defined threshold' do
available_capacity = mirror_capacity_threshold - 1
expect(described_class).to receive(:available_capacity).and_return(available_capacity)
expect(described_class).not_to receive(:mirrors_ready_to_sync_count)
expect(described_class.reschedule_immediately?).to eq(false)
it 'returns false' do
expect(described_class.reschedule_immediately?).to be_falsey
end
after do
Gitlab::Redis::SharedState.with { |redis| redis.del(Gitlab::Mirror::PULL_CAPACITY_KEY) }
end
end
......
# frozen_string_literal: true
require 'rails_helper'
describe ProjectImportState, type: :model do
......@@ -441,14 +443,14 @@ describe ProjectImportState, type: :model do
expect(import_state.force_import_job!).to be_nil
end
it 'sets next execution timestamp to now and schedules UpdateAllMirrorsWorker' do
timestamp = 1.second.from_now.change(usec: 0)
it 'sets next execution timestamp to 5 minutes ago and schedules UpdateAllMirrorsWorker' do
timestamp = Time.now
import_state = create(:import_state, :mirror)
expect(UpdateAllMirrorsWorker).to receive(:perform_async)
Timecop.freeze(timestamp) do
expect { import_state.force_import_job! }.to change(import_state, :next_execution_timestamp).to(timestamp)
expect { import_state.force_import_job! }.to change(import_state, :next_execution_timestamp).to(5.minutes.ago)
end
end
......@@ -461,7 +463,7 @@ describe ProjectImportState, type: :model do
Timecop.freeze(timestamp) do
expect { import_state.force_import_job! }.to change(import_state, :retry_count).to(0)
expect(import_state.next_execution_timestamp).to be_like_time(timestamp)
expect(import_state.next_execution_timestamp).to be_like_time(5.minutes.ago)
end
end
end
......
......@@ -157,15 +157,17 @@ describe API::Namespaces do
end
end
context 'when namespace has a value for last_ci_minutes_notification_at' do
[:last_ci_minutes_notification_at, :last_ci_minutes_usage_notification_level].each do |attr|
context "when namespace has a value for #{attr}" do
before do
group1.update_attribute(:last_ci_minutes_notification_at, Time.now)
group1.update_attribute(attr, Time.now)
end
it 'resets that value when assigning extra CI minutes' do
expect do
put api("/namespaces/#{group1.full_path}", admin), params: { plan: 'silver', extra_shared_runners_minutes_limit: 1000 }
end.to change { group1.reload.last_ci_minutes_notification_at }.to(nil)
end.to change { group1.reload.send(attr) }.to(nil)
end
end
end
end
......
......@@ -16,19 +16,13 @@ describe CiMinutesUsageNotifyService do
shared_examples 'namespace with all CI minutes used' do
context 'when usage is over the quote' do
it 'sends the email to the owner' do
expect(CiMinutesUsageMailer).to receive(:notify).once.with(namespace.name, user.email).and_return(spy)
expect(CiMinutesUsageMailer).to receive(:notify).once.with(namespace.name, [user.email]).and_return(spy)
subject
end
end
end
describe '#execute' do
let(:extra_ci_minutes) { 0 }
let(:namespace) do
create(:namespace, shared_runners_minutes_limit: 2000, extra_shared_runners_minutes_limit: extra_ci_minutes)
end
let(:project) { create(:project, namespace: namespace) }
let(:user) { create(:user) }
let(:user_2) { create(:user) }
......@@ -38,6 +32,12 @@ describe CiMinutesUsageNotifyService do
create(:namespace_statistics, namespace: namespace, shared_runners_seconds: ci_minutes_used * 60)
end
describe '#execute' do
let(:extra_ci_minutes) { 0 }
let(:namespace) do
create(:namespace, shared_runners_minutes_limit: 2000, extra_shared_runners_minutes_limit: extra_ci_minutes)
end
subject { described_class.new(project).execute }
context 'with a personal namespace' do
......@@ -99,8 +99,9 @@ describe CiMinutesUsageNotifyService do
let(:ci_minutes_used) { 2001 }
it 'sends the email to all the owners' do
expect(CiMinutesUsageMailer).to receive(:notify).with(namespace.name, user.email).and_return(spy)
expect(CiMinutesUsageMailer).to receive(:notify).with(namespace.name, user_2.email).and_return(spy)
expect(CiMinutesUsageMailer).to receive(:notify)
.with(namespace.name, match_array([user_2.email, user.email]))
.and_return(spy)
subject
end
......@@ -120,4 +121,72 @@ describe CiMinutesUsageNotifyService do
end
end
end
describe 'CI usage limit approaching' do
let(:namespace) { create(:group, shared_runners_minutes_limit: 2000) }
def notify_owners
described_class.new(project).execute
end
shared_examples 'no notification is sent' do
it 'does not notify owners' do
expect(CiMinutesUsageMailer).not_to receive(:notify_limit)
notify_owners
end
end
shared_examples 'notification for custom level is sent' do |minutes_used, expected_level|
before do
namespace_statistics.update_attribute(:shared_runners_seconds, minutes_used * 60)
end
it 'notifies the the owners about it' do
expect(CiMinutesUsageMailer).to receive(:notify_limit)
.with(namespace.name, array_including(user_2.email, user.email), expected_level)
.and_call_original
notify_owners
end
end
before do
stub_const("EE::Namespace::CI_USAGE_ALERT_LEVELS", [30, 5])
namespace.add_owner(user)
namespace.add_owner(user_2)
end
context 'when available minutes are above notification levels' do
let(:ci_minutes_used) { 1000 }
it_behaves_like 'no notification is sent'
end
context 'when available minutes have reached the first level of alert' do
it_behaves_like 'notification for custom level is sent', 1500, 30
context 'when other Pipeline has finished but second level of alert has not been reached' do
before do
namespace_statistics.update_attribute(:shared_runners_seconds, 1500 * 60)
notify_owners
namespace_statistics.update_attribute(:shared_runners_seconds, 1600 * 60)
end
it_behaves_like 'no notification is sent'
end
end
context 'when available minutes have reached the second level of alert' do
it_behaves_like 'notification for custom level is sent', 1500, 30
it_behaves_like 'notification for custom level is sent', 1980, 5
end
context 'when there are not available minutes to use' do
include_examples 'no notification is sent'
end
end
end
......@@ -37,7 +37,7 @@ describe BuildFinishedWorker do
namespace.update_attribute(:shared_runners_minutes_limit, 2000)
namespace_stats.update_attribute(:shared_runners_seconds, 2100 * 60)
expect(CiMinutesUsageMailer).to receive(:notify).once.with(namespace.name, namespace.owner.email).and_return(spy)
expect(CiMinutesUsageMailer).to receive(:notify).once.with(namespace.name, [namespace.owner.email]).and_return(spy)
subject
end
......
......@@ -90,6 +90,22 @@ describe ClearSharedRunnersMinutesWorker do
end
end
end
[:last_ci_minutes_notification_at, :last_ci_minutes_usage_notification_level].each do |attr|
context "when #{attr} is present" do
before do
namespace.update_attribute(attr, Time.now)
end
it 'nullifies the field' do
expect(namespace.send(attr)).to be_present
subject
expect(namespace.reload.send(attr)).not_to be_present
end
end
end
end
end
end
......@@ -26,33 +26,60 @@ describe UpdateAllMirrorsWorker do
end
it 'schedules mirrors' do
expect(worker).to receive(:schedule_mirrors!)
expect(worker).to receive(:schedule_mirrors!).and_call_original
worker.perform
end
context 'when updates were scheduled' do
before do
allow(worker).to receive(:schedule_mirrors!).and_return(1)
end
it 'sleeps a bit after scheduling mirrors' do
expect(Kernel).to receive(:sleep).with(described_class::RESCHEDULE_WAIT)
worker.perform
end
it 'reschedules the job if capacity is left' do
context 'if capacity is available' do
before do
allow(Gitlab::Mirror).to receive(:reschedule_immediately?).and_return(true)
end
it 'reschedules the job' do
expect(described_class).to receive(:perform_async)
worker.perform
end
end
context 'if no capacity is available' do
before do
allow(Gitlab::Mirror).to receive(:reschedule_immediately?).and_return(false)
end
it 'does not reschedule the job' do
expect(described_class).not_to receive(:perform_async)
worker.perform
end
end
end
it 'does not reschedule the job if no capacity left' do
context 'when no updates were scheduled' do
before do
allow(worker).to receive(:schedule_mirrors!).and_return(0)
allow(Gitlab::Mirror).to receive(:reschedule_immediately?).and_return(false)
end
it 'does not reschedule the job' do
expect(described_class).not_to receive(:perform_async)
worker.perform
end
end
end
describe '#schedule_mirrors!' do
def schedule_mirrors!(capacity:)
......
......@@ -115,6 +115,8 @@ module API
params do
requires :id, type: Integer, desc: 'The ID of the runner'
optional :status, type: String, desc: 'Status of the job', values: Ci::Build::AVAILABLE_STATUSES
optional :order_by, type: String, desc: 'Order by `id` or not', values: RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS
optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)'
use :pagination
end
get ':id/jobs' do
......
......@@ -10,9 +10,9 @@ module Gitlab
RedirectionTooDeep = Class.new(StandardError)
HTTP_ERRORS = [
SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET,
Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout,
Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError,
SocketError, OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError,
Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError,
Gitlab::HTTP::RedirectionTooDeep
].freeze
......
......@@ -9848,6 +9848,12 @@ msgstr ""
msgid "Pipelines| to purchase more minutes."
msgstr ""
msgid "Pipelines|%{namespace_name} has exceeded its pipeline minutes quota."
msgstr ""
msgid "Pipelines|%{namespace_name} has less than %{notification_level}%% of CI minutes available."
msgstr ""
msgid "Pipelines|API"
msgstr ""
......
......@@ -35,5 +35,27 @@ describe RunnerJobsFinder do
end
end
end
context 'when order_by and sort are specified' do
context 'when order_by id and sort is asc' do
let(:params) { { order_by: 'id', sort: 'asc' } }
let!(:jobs) { create_list(:ci_build, 2, runner: runner, project: project, user: create(:user)) }
it 'sorts as id: :asc' do
is_expected.to eq(jobs.sort_by(&:id))
end
end
end
context 'when order_by is specified and sort is not specified' do
context 'when order_by id and sort is not specified' do
let(:params) { { order_by: 'id' } }
let!(:jobs) { create_list(:ci_build, 2, runner: runner, project: project, user: create(:user)) }
it 'sorts as id: :desc' do
is_expected.to eq(jobs.sort_by(&:id).reverse)
end
end
end
end
end
......@@ -101,6 +101,15 @@ describe DroneCiService, :use_clean_rails_memory_store_caching do
is_expected.to eq(:error)
end
Gitlab::HTTP::HTTP_ERRORS.each do |http_error|
it "sets commit status to :error with a #{http_error.name} error" do
WebMock.stub_request(:get, commit_status_path)
.to_raise(http_error)
is_expected.to eq(:error)
end
end
{
"killed" => :canceled,
"failure" => :failed,
......
......@@ -584,6 +584,34 @@ describe API::Runners do
end
end
context 'when valid order_by is provided' do
context 'when sort order is not specified' do
it 'return jobs in descending order' do
get api("/runners/#{project_runner.id}/jobs?order_by=id", admin)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
expect(json_response.length).to eq(2)
expect(json_response.first).to include('id' => job_5.id)
end
end
context 'when sort order is specified as asc' do
it 'return jobs sorted in ascending order' do
get api("/runners/#{project_runner.id}/jobs?order_by=id&sort=asc", admin)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
expect(json_response.length).to eq(2)
expect(json_response.first).to include('id' => job_4.id)
end
end
end
context 'when invalid status is provided' do
it 'return 400' do
get api("/runners/#{project_runner.id}/jobs?status=non-existing", admin)
......@@ -591,6 +619,22 @@ describe API::Runners do
expect(response).to have_gitlab_http_status(400)
end
end
context 'when invalid order_by is provided' do
it 'return 400' do
get api("/runners/#{project_runner.id}/jobs?order_by=non-existing", admin)
expect(response).to have_gitlab_http_status(400)
end
end
context 'when invalid sort is provided' do
it 'return 400' do
get api("/runners/#{project_runner.id}/jobs?sort=non-existing", admin)
expect(response).to have_gitlab_http_status(400)
end
end
end
context "when runner doesn't exist" do
......
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