提交 60082b33 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 ce493944
......@@ -135,8 +135,11 @@ export default {
:label-for="fieldId"
:invalid-feedback="__('This field is required.')"
:state="valid"
:description="help"
>
<template #description>
<span v-html="help"></span>
</template>
<template v-if="isCheckbox">
<input :name="fieldName" type="hidden" value="false" />
<gl-form-checkbox v-model="model" v-bind="sharedProps">
......
......@@ -54,9 +54,8 @@ module IntegrationsActions
end
def integration
# Using instance variable `@service` still required as it's used in ServiceParams
# and app/views/shared/_service_settings.html.haml. Should be removed once
# those 2 are refactored to use `@integration`.
# Using instance variable `@service` still required as it's used in ServiceParams.
# Should be removed once that is refactored to use `@integration`.
@integration = @service ||= find_or_initialize_integration(params[:id]) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
......
......@@ -23,6 +23,7 @@
# min_access_level: integer
# last_activity_after: datetime
# last_activity_before: datetime
# repository_storage: string
#
class ProjectsFinder < UnionFinder
include CustomAttributesFilter
......@@ -75,6 +76,7 @@ class ProjectsFinder < UnionFinder
collection = by_deleted_status(collection)
collection = by_last_activity_after(collection)
collection = by_last_activity_before(collection)
collection = by_repository_storage(collection)
collection
end
......@@ -197,6 +199,14 @@ class ProjectsFinder < UnionFinder
end
end
def by_repository_storage(items)
if params[:repository_storage].present?
items.where(repository_storage: params[:repository_storage]) # rubocop: disable CodeReuse/ActiveRecord
else
items
end
end
def sort(items)
params[:sort].present? ? items.sort_by_attribute(params[:sort]) : items.projects_order_id_desc
end
......
......@@ -44,13 +44,6 @@ module ServicesHelper
end
end
def event_action_description(action)
case action
when "comment"
s_("ProjectService|Comment will be posted on each event")
end
end
def service_save_button
button_tag(class: 'btn btn-success', type: 'submit', data: { qa_selector: 'save_changes_button' }) do
icon('spinner spin', class: 'hidden js-btn-spinner') +
......@@ -90,7 +83,7 @@ module ServicesHelper
def scoped_test_integration_path(integration)
if @project.present?
test_project_settings_integration_path(@project, integration)
test_project_service_path(@project, integration)
elsif @group.present?
test_group_settings_integration_path(@group, integration)
else
......@@ -102,22 +95,36 @@ module ServicesHelper
Feature.enabled?(:integration_form_refactor, @project)
end
def trigger_events_for_service
def integration_form_data(integration)
{
show_active: integration.show_active_box?.to_s,
activated: (integration.active || integration.new_record?).to_s,
type: integration.to_param,
merge_request_events: integration.merge_requests_events.to_s,
commit_events: integration.commit_events.to_s,
enable_comments: integration.comment_on_event_enabled.to_s,
comment_detail: integration.comment_detail,
trigger_events: trigger_events_for_service(integration),
fields: fields_for_service(integration)
}
end
def trigger_events_for_service(integration)
return [] unless integration_form_refactor?
ServiceEventSerializer.new(service: @service).represent(@service.configurable_events).to_json
ServiceEventSerializer.new(service: integration).represent(integration.configurable_events).to_json
end
def fields_for_service
def fields_for_service(integration)
return [] unless integration_form_refactor?
ServiceFieldSerializer.new(service: @service).represent(@service.global_fields).to_json
ServiceFieldSerializer.new(service: integration).represent(integration.global_fields).to_json
end
def show_service_trigger_events?
return false if @service.is_a?(JiraService) || integration_form_refactor?
def show_service_trigger_events?(integration)
return false if integration.is_a?(JiraService) || integration_form_refactor?
@service.configurable_events.present?
integration.configurable_events.present?
end
extend self
......
......@@ -1925,6 +1925,7 @@ class Project < ApplicationRecord
.append(key: 'CI_PROJECT_PATH', value: full_path)
.append(key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug)
.append(key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path)
.append(key: 'CI_PROJECT_ROOT_NAMESPACE', value: namespace.root_ancestor.path)
.append(key: 'CI_PROJECT_URL', value: web_url)
.append(key: 'CI_PROJECT_VISIBILITY', value: Gitlab::VisibilityLevel.string_level(visibility_level))
.append(key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: repository_languages.map(&:name).join(',').downcase)
......
......@@ -105,6 +105,9 @@ class GlobalPolicy < BasePolicy
enable :update_custom_attribute
end
# We can't use `read_statistics` because the user may have different permissions for different projects
rule { admin }.enable :use_project_statistics_filters
rule { external_user }.prevent :create_snippet
end
......
......@@ -80,7 +80,7 @@ module Metrics
def fetch_dashboard
uid = GrafanaUidParser.new(grafana_url, project).parse
raise DashboardProcessingError.new('Dashboard uid not found') unless uid
raise DashboardProcessingError.new(_('Dashboard uid not found')) unless uid
response = client.get_dashboard(uid: uid)
......@@ -89,7 +89,7 @@ module Metrics
def fetch_datasource(dashboard)
name = DatasourceNameParser.new(grafana_url, dashboard).parse
raise DashboardProcessingError.new('Datasource name not found') unless name
raise DashboardProcessingError.new(_('Datasource name not found')) unless name
response = client.get_datasource(name: name)
......@@ -115,7 +115,7 @@ module Metrics
def parse_json(json)
Gitlab::Json.parse(json, symbolize_names: true)
rescue JSON::ParserError
raise DashboardProcessingError.new('Grafana response contains invalid json')
raise DashboardProcessingError.new(_('Grafana response contains invalid json'))
end
end
......
......@@ -39,7 +39,7 @@ module Metrics
end
def invalid_embed_json!(message)
raise DashboardProcessingError.new("Parsing error for param :embed_json. #{message}")
raise DashboardProcessingError.new(_("Parsing error for param :embed_json. %{message}") % { message: message })
end
end
end
......
......@@ -4,7 +4,7 @@
%p #{@service.description} template.
= form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'fieldset-form' } do |form|
= render 'shared/service_settings', form: form, service: @service
= render 'shared/service_settings', form: form, integration: @service
.footer-block.row-content-block
= form.submit 'Save', class: 'btn btn-success'
- add_to_breadcrumbs "Service Templates", admin_application_settings_services_path
- breadcrumb_title @service.title
- page_title @service.title, "Service Templates"
- @content_class = 'limit-container-width' unless fluid_layout
= render 'form'
......@@ -216,7 +216,7 @@
%strong.fly-out-top-item-name
= _('Appearance')
= nav_link(controller: :application_settings) do
= nav_link(controller: [:application_settings, :integrations]) do
= link_to general_admin_application_settings_path do
.nav-icon-container
= sprite_icon('settings')
......@@ -224,7 +224,7 @@
= _('Settings')
%ul.sidebar-sub-level-items.qa-admin-sidebar-settings-submenu
= nav_link(controller: :application_settings, html_options: { class: "fly-out-top-item" } ) do
= nav_link(controller: [:application_settings, :integrations], html_options: { class: "fly-out-top-item" } ) do
= link_to general_admin_application_settings_path do
%strong.fly-out-top-item-name
= _('Settings')
......@@ -233,7 +233,7 @@
= link_to general_admin_application_settings_path, title: _('General'), class: 'qa-admin-settings-general-item' do
%span
= _('General')
= nav_link(path: 'application_settings#integrations') do
= nav_link(path: ['application_settings#integrations', 'integrations#edit']) do
= link_to integrations_admin_application_settings_path, title: _('Integrations'), data: { qa_selector: 'integration_settings_link' } do
%span
= _('Integrations')
......
......@@ -11,7 +11,7 @@
%p= @service.detailed_description
.col-lg-8
= form_for(@service, as: :service, url: scoped_integration_path(@service), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= render 'shared/service_settings', form: form, service: @service
= render 'shared/service_settings', form: form, integration: @service
.footer-block.row-content-block
%input{ id: 'services_redirect_to', type: 'hidden', name: 'redirect_to', value: request.referrer }
= service_save_button
......
= form_errors(@service)
= form_errors(integration)
- if lookup_context.template_exists?('help', "projects/services/#{@service.to_param}", true)
= render "projects/services/#{@service.to_param}/help", subject: @service
- elsif @service.help.present?
- if lookup_context.template_exists?('help', "projects/services/#{integration.to_param}", true)
= render "projects/services/#{integration.to_param}/help", subject: integration
- elsif integration.help.present?
.info-well
.well-segment
= markdown @service.help
= markdown integration.help
.service-settings
.js-vue-integration-settings{ data: { show_active: @service.show_active_box?.to_s, activated: (@service.active || @service.new_record?).to_s, type: @service.to_param, merge_request_events: @service.merge_requests_events.to_s,
commit_events: @service.commit_events.to_s, enable_comments: @service.comment_on_event_enabled.to_s, comment_detail: @service.comment_detail, trigger_events: trigger_events_for_service, fields: fields_for_service } }
.js-vue-integration-settings{ data: integration_form_data(integration) }
- if show_service_trigger_events?
- if show_service_trigger_events?(integration)
.form-group.row
%label.col-form-label.col-sm-2= _('Trigger')
.col-sm-10
- @service.configurable_events.each do |event|
- integration.configurable_events.each do |event|
.form-group
.form-check
= form.check_box service_event_field_name(event), class: 'form-check-input'
......@@ -24,14 +23,14 @@ commit_events: @service.commit_events.to_s, enable_comments: @service.comment_on
%strong
= event.humanize
- field = @service.event_field(event)
- field = integration.event_field(event)
- if field
= form.text_field field[:name], class: "form-control", placeholder: field[:placeholder]
%p.text-muted
= @service.class.event_description(event)
= integration.class.event_description(event)
- unless integration_form_refactor?
- @service.global_fields.each do |field|
- integration.global_fields.each do |field|
= render 'shared/field', form: form, field: field
- add_to_breadcrumbs _('Integrations'), scoped_integrations_path
- breadcrumb_title @integration.title
- page_title @integration.title, _('Integrations')
- @content_class = 'limit-container-width' unless fluid_layout
= render 'shared/integrations/form', integration: @integration
---
title: Refine UI of integration form
merge_request: 34707
author:
type: changed
---
title: Add CI_PROJECT_ROOT_NAMESPACE predefined environment variable
merge_request: 34733
author:
type: added
---
title: Allow advanced API projects filtering for admins
merge_request: 32879
author:
type: added
---
title: Fix Gitaly duration timings of BlobService RPCs
merge_request: 34906
author:
type: other
# frozen_string_literal: true
class AddIndexOnRepositorySizeAndProjectIdToProjectStatistics < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :project_statistics, [:repository_size, :project_id]
end
def down
remove_concurrent_index :project_statistics, [:repository_size, :project_id]
end
end
# frozen_string_literal: true
class AddIndexOnStorageSizeAndProjectIdToProjectStatistics < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :project_statistics, [:storage_size, :project_id]
end
def down
remove_concurrent_index :project_statistics, [:storage_size, :project_id]
end
end
# frozen_string_literal: true
class AddIndexOnWikiSizeAndProjectIdToProjectStatistics < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :project_statistics, [:wiki_size, :project_id]
end
def down
remove_concurrent_index :project_statistics, [:wiki_size, :project_id]
end
end
......@@ -10643,6 +10643,12 @@ CREATE INDEX index_project_statistics_on_namespace_id ON public.project_statisti
CREATE UNIQUE INDEX index_project_statistics_on_project_id ON public.project_statistics USING btree (project_id);
CREATE INDEX index_project_statistics_on_repository_size_and_project_id ON public.project_statistics USING btree (repository_size, project_id);
CREATE INDEX index_project_statistics_on_storage_size_and_project_id ON public.project_statistics USING btree (storage_size, project_id);
CREATE INDEX index_project_statistics_on_wiki_size_and_project_id ON public.project_statistics USING btree (wiki_size, project_id);
CREATE UNIQUE INDEX index_project_tracing_settings_on_project_id ON public.project_tracing_settings USING btree (project_id);
CREATE INDEX index_projects_api_created_at_id_desc ON public.projects USING btree (created_at, id DESC);
......@@ -14055,6 +14061,9 @@ COPY "schema_migrations" (version) FROM STDIN;
20200604174558
20200605003204
20200605093113
20200605160806
20200605160836
20200605160851
20200608072931
20200608075553
20200608214008
......
......@@ -103,10 +103,10 @@ Some feature flags can be enabled or disabled on a per project basis:
Feature.enable(:<feature flag>, Project.find(<project id>))
```
For example, to enable the [`:release_evidence_collection`](../ci/junit_test_reports.md#enabling-the-feature) feature flag for project `1234`:
For example, to enable the [`:junit_pipeline_view`](../ci/junit_test_reports.md#enabling-the-junit-test-reports-feature-core-only) feature flag for project `1234`:
```ruby
Feature.enable(:release_evidence_collection, Project.find(1234))
Feature.enable(:junit_pipeline_view, Project.find(1234))
```
When the feature is ready, GitLab will remove the feature flag, the option for
......
......@@ -142,7 +142,7 @@ Example of response
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202525) in GitLab 13.0.
CAUTION: **Caution:**
This API route is part of the [JUnit test report](../ci/junit_test_reports.md) feature. It is protected by a [feature flag](../development/feature_flags/index.md) that is **disabled** due to performance issues with very large data sets. See [the documentation for the feature](../ci/junit_test_reports.md#enabling-the-feature) for further details.
This API route is part of the [JUnit test report](../ci/junit_test_reports.md) feature. It is protected by a [feature flag](../development/feature_flags/index.md) that is **disabled** due to performance issues with very large data sets.
```plaintext
GET /projects/:id/pipelines/:pipeline_id/test_report
......
......@@ -45,7 +45,7 @@ GET /projects
| --------- | ---- | -------- | ----------- |
| `archived` | boolean | no | Limit by archived status |
| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. `repository_size`, `storage_size`, or `wiki_size` fields are only allowed for admins. Default is `created_at` |
| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of projects matching the search criteria |
| `search_namespaces` | boolean | no | Include ancestor namespaces when matching search criteria. Default is `false` |
......@@ -65,6 +65,7 @@ GET /projects
| `id_before` | integer | no | Limit results to projects with IDs less than the specified ID |
| `last_activity_after` | datetime | no | Limit results to projects with last_activity after specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ |
| `last_activity_before` | datetime | no | Limit results to projects with last_activity before specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ |
| `repository_storage` | string | no | Limit results to projects stored on repository_storage. Available for admins only. |
NOTE: **Note:**
This endpoint supports [keyset pagination](README.md#keyset-based-pagination) for selected `order_by` options.
......
......@@ -206,7 +206,7 @@ cunit:
junit: ./my-cunit-test.xml
```
### .Net example
### .NET example
The [JunitXML.TestLogger](https://www.nuget.org/packages/JunitXml.TestLogger/) NuGet
package can generate test reports for .Net Framework and .Net Core applications. The following
......@@ -234,7 +234,9 @@ Test:
## Viewing JUnit test reports on GitLab
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24792) in GitLab 12.5.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24792) in GitLab 12.5.
> - It's deployed behind a feature flag, disabled by default.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enabling-the-junit-test-reports-feature-core-only). **(CORE ONLY)**
If JUnit XML files are generated and uploaded as part of a pipeline, these reports
can be viewed inside the pipelines details page. The **Tests** tab on this page will
......@@ -248,7 +250,7 @@ with failed showing at the top, skipped next and successful cases last.
You can also retrieve the reports via the [GitLab API](../api/pipelines.md#get-a-pipelines-test-report).
### Enabling the feature
### Enabling the JUnit test reports feature **(CORE ONLY)**
This feature comes with the `:junit_pipeline_view` feature flag disabled by default. This
feature is disabled due to some performance issues with very large data sets.
......@@ -266,7 +268,9 @@ Feature.enable(:junit_pipeline_view, Project.find(<your-project-id-here>))
## Viewing JUnit screenshots on GitLab
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202114) in GitLab 13.0.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202114) in GitLab 13.0.
> - It's deployed behind a feature flag, disabled by default.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enabling-the-junit-screenshots-feature-core-only). **(CORE ONLY)**
If JUnit XML files contain an `attachment` tag, GitLab parses the attachment.
......@@ -280,7 +284,7 @@ Upload your screenshots as [artifacts](pipelines/job_artifacts.md#artifactsrepor
When [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/6061) is complete, the attached file will be visible on the pipeline details page.
### Enabling the feature
### Enabling the JUnit screenshots feature **(CORE ONLY)**
This feature comes with the `:junit_pipeline_screenshots_view` feature flag disabled by default.
......
......@@ -341,6 +341,7 @@ export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-foss"
export CI_PROJECT_NAME="gitlab-foss"
export CI_PROJECT_TITLE="GitLab FOSS"
export CI_PROJECT_NAMESPACE="gitlab-org"
export CI_PROJECT_ROOT_NAMESPACE="gitlab-org"
export CI_PROJECT_PATH="gitlab-org/gitlab-foss"
export CI_PROJECT_URL="https://example.com/gitlab-org/gitlab-foss"
export CI_REGISTRY="registry.example.com"
......@@ -909,6 +910,8 @@ if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then
++ CI_PROJECT_PATH_SLUG=gitlab-examples-ci-debug-trace
++ export CI_PROJECT_NAMESPACE=gitlab-examples
++ CI_PROJECT_NAMESPACE=gitlab-examples
++ export CI_PROJECT_ROOT_NAMESPACE=gitlab-examples
++ CI_PROJECT_ROOT_NAMESPACE=gitlab-examples
++ export CI_PROJECT_URL=https://gitlab.com/gitlab-examples/ci-debug-trace
++ CI_PROJECT_URL=https://gitlab.com/gitlab-examples/ci-debug-trace
++ export CI_PROJECT_VISIBILITY=public
......
......@@ -99,6 +99,7 @@ You can add a command to your `.gitlab-ci.yml` file to
| `CI_PROJECT_ID` | all | all | The unique ID of the current project that GitLab CI/CD uses internally |
| `CI_PROJECT_NAME` | 8.10 | 0.5 | The name of the directory for the project that is currently being built. For example, if the project URL is `gitlab.example.com/group-name/project-1`, the `CI_PROJECT_NAME` would be `project-1`. |
| `CI_PROJECT_NAMESPACE` | 8.10 | 0.5 | The project namespace (username or group name) that is currently being built |
| `CI_PROJECT_ROOT_NAMESPACE` | 13.2 | 0.5 | The **root** project namespace (username or group name) that is currently being built. For example, if `CI_PROJECT_NAME` is `root-group/child-group/grandchild-group`, `CI_PROJECT_ROOT_NAMESPACE` would be `root-group`. |
| `CI_PROJECT_PATH` | 8.10 | 0.5 | The namespace with project name |
| `CI_PROJECT_PATH_SLUG` | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
| `CI_PROJECT_REPOSITORY_LANGUAGES` | 12.3 | all | Comma-separated, lowercased list of the languages used in the repository (e.g. `ruby,javascript,html,css`) |
......
......@@ -543,6 +543,7 @@ module API
finder_params[:id_before] = params[:id_before] if params[:id_before]
finder_params[:last_activity_after] = params[:last_activity_after] if params[:last_activity_after]
finder_params[:last_activity_before] = params[:last_activity_before] if params[:last_activity_before]
finder_params[:repository_storage] = params[:repository_storage] if params[:repository_storage]
finder_params
end
......
......@@ -6,6 +6,8 @@ module API
extend ActiveSupport::Concern
extend Grape::API::Helpers
STATISTICS_SORT_PARAMS = %w[storage_size repository_size wiki_size].freeze
params :optional_project_params_ce do
optional :description, type: String, desc: 'The description of the project'
optional :build_git_strategy, type: String, values: %w(fetch clone), desc: 'The Git strategy. Defaults to `fetch`'
......
......@@ -17,6 +17,7 @@ module API
projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
projects = projects.with_statistics if params[:statistics]
projects = projects.joins(:statistics) if params[:order_by].include?('project_statistics') # rubocop: disable CodeReuse/ActiveRecord
lang = params[:with_programming_language]
projects = projects.with_programming_language(lang) if lang
......@@ -28,6 +29,20 @@ module API
attrs.delete(:repository_storage) unless can?(current_user, :change_repository_storage, project)
end
def verify_project_filters!(attrs)
attrs.delete(:repository_storage) unless can?(current_user, :use_project_statistics_filters)
end
def verify_statistics_order_by_projects!
return unless Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS.include?(params[:order_by])
params[:order_by] = if can?(current_user, :use_project_statistics_filters)
"project_statistics.#{params[:order_by]}"
else
route.params['order_by'][:default]
end
end
def delete_project(user_project)
destroy_conditionally!(user_project) do
::Projects::DestroyService.new(user_project, current_user, {}).async_execute
......@@ -52,8 +67,9 @@ module API
end
params :sort_params do
optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
default: 'created_at', desc: 'Return projects ordered by field'
optional :order_by, type: String,
values: %w[id name path created_at updated_at last_activity_at] + Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS,
default: 'created_at', desc: "Return projects ordered by field. #{Helpers::ProjectsHelpers::STATISTICS_SORT_PARAMS.join(', ')} are only available to admins."
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return projects sorted in ascending and descending order'
end
......@@ -75,6 +91,7 @@ module API
optional :id_before, type: Integer, desc: 'Limit results to projects with IDs less than the specified ID'
optional :last_activity_after, type: DateTime, desc: 'Limit results to projects with last_activity after specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
optional :last_activity_before, type: DateTime, desc: 'Limit results to projects with last_activity before specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins'
use :optional_filter_params_ee
end
......@@ -88,10 +105,15 @@ module API
end
def load_projects
ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
params = project_finder_params
verify_project_filters!(params)
ProjectsFinder.new(current_user: current_user, params: params).execute
end
def present_projects(projects, options = {})
verify_statistics_order_by_projects!
projects = reorder_projects(projects)
projects = apply_filters(projects)
......
......@@ -60,12 +60,20 @@ module Gitlab
end
end
def diff_file_with_old_path(old_path)
diff_files.find { |diff_file| diff_file.old_path == old_path }
def diff_file_with_old_path(old_path, a_mode = nil)
if Feature.enabled?(:file_identifier_hash) && a_mode.present?
diff_files.find { |diff_file| diff_file.old_path == old_path && diff_file.a_mode == a_mode }
else
diff_files.find { |diff_file| diff_file.old_path == old_path }
end
end
def diff_file_with_new_path(new_path)
diff_files.find { |diff_file| diff_file.new_path == new_path }
def diff_file_with_new_path(new_path, b_mode = nil)
if Feature.enabled?(:file_identifier_hash) && b_mode.present?
diff_files.find { |diff_file| diff_file.new_path == new_path && diff_file.b_mode == b_mode }
else
diff_files.find { |diff_file| diff_file.new_path == new_path }
end
end
def clear_cache
......
......@@ -42,6 +42,10 @@ module Gitlab
@cd_diffs ||= compare(new_diff_refs.start_sha, new_diff_refs.head_sha)
end
def diff_file(position)
position.diff_file(project.repository)
end
private
def compare(start_sha, head_sha, straight: false)
......
......@@ -8,6 +8,7 @@ module Gitlab
delegate \
:project,
:diff_file,
:ac_diffs,
:bd_diffs,
:cd_diffs,
......
......@@ -5,22 +5,26 @@ module Gitlab
class PositionTracer
class ImageStrategy < BaseStrategy
def trace(position)
a_path = position.old_path
b_path = position.new_path
diff_file = diff_file(position)
a_mode = diff_file&.a_mode
b_mode = diff_file&.b_mode
# If file exists in B->D (e.g. updated, renamed, removed), let the
# note become outdated.
bd_diff = bd_diffs.diff_file_with_old_path(b_path)
bd_diff = bd_diffs.diff_file_with_old_path(b_path, b_mode)
return { position: new_position(position, bd_diff), outdated: true } if bd_diff
# If file still exists in the new diff, update the position.
cd_diff = cd_diffs.diff_file_with_new_path(bd_diff&.new_path || b_path)
cd_diff = cd_diffs.diff_file_with_new_path(b_path, b_mode)
return { position: new_position(position, cd_diff), outdated: false } if cd_diff
# If file exists in A->C (e.g. rebased and same changes were present
# in target branch), let the note become outdated.
ac_diff = ac_diffs.diff_file_with_old_path(position.old_path)
ac_diff = ac_diffs.diff_file_with_old_path(a_path, a_mode)
return { position: new_position(position, ac_diff), outdated: true } if ac_diff
......
......@@ -76,16 +76,20 @@ module Gitlab
def trace_added_line(position)
b_path = position.new_path
b_line = position.new_line
diff_file = diff_file(position)
b_mode = diff_file&.b_mode
bd_diff = bd_diffs.diff_file_with_old_path(b_path)
bd_diff = bd_diffs.diff_file_with_old_path(b_path, b_mode)
d_path = bd_diff&.new_path || b_path
d_mode = bd_diff&.b_mode || b_mode
d_line = LineMapper.new(bd_diff).old_to_new(b_line)
if d_line
cd_diff = cd_diffs.diff_file_with_new_path(d_path)
cd_diff = cd_diffs.diff_file_with_new_path(d_path, d_mode)
c_path = cd_diff&.old_path || d_path
c_mode = cd_diff&.a_mode || d_mode
c_line = LineMapper.new(cd_diff).new_to_old(d_line)
if c_line
......@@ -98,7 +102,7 @@ module Gitlab
else
# If the line is no longer in the MR, we unfortunately cannot show
# the current state on the CD diff, so we treat it as outdated.
ac_diff = ac_diffs.diff_file_with_new_path(c_path)
ac_diff = ac_diffs.diff_file_with_new_path(c_path, c_mode)
{ position: new_position(ac_diff, nil, c_line), outdated: true }
end
......@@ -115,22 +119,26 @@ module Gitlab
def trace_removed_line(position)
a_path = position.old_path
a_line = position.old_line
diff_file = diff_file(position)
a_mode = diff_file&.a_mode
ac_diff = ac_diffs.diff_file_with_old_path(a_path)
ac_diff = ac_diffs.diff_file_with_old_path(a_path, a_mode)
c_path = ac_diff&.new_path || a_path
c_mode = ac_diff&.b_mode || a_mode
c_line = LineMapper.new(ac_diff).old_to_new(a_line)
if c_line
cd_diff = cd_diffs.diff_file_with_old_path(c_path)
cd_diff = cd_diffs.diff_file_with_old_path(c_path, c_mode)
d_path = cd_diff&.new_path || c_path
d_mode = cd_diff&.b_mode || c_mode
d_line = LineMapper.new(cd_diff).old_to_new(c_line)
if d_line
# If the line is still in C but also in D, it has turned from a
# removed line into an unchanged one.
bd_diff = bd_diffs.diff_file_with_new_path(d_path)
bd_diff = bd_diffs.diff_file_with_new_path(d_path, d_mode)
{ position: new_position(bd_diff, nil, d_line), outdated: true }
else
......@@ -148,17 +156,21 @@ module Gitlab
a_line = position.old_line
b_path = position.new_path
b_line = position.new_line
diff_file = diff_file(position)
a_mode = diff_file&.a_mode
b_mode = diff_file&.b_mode
ac_diff = ac_diffs.diff_file_with_old_path(a_path)
ac_diff = ac_diffs.diff_file_with_old_path(a_path, a_mode)
c_path = ac_diff&.new_path || a_path
c_mode = ac_diff&.b_mode || a_mode
c_line = LineMapper.new(ac_diff).old_to_new(a_line)
bd_diff = bd_diffs.diff_file_with_old_path(b_path)
bd_diff = bd_diffs.diff_file_with_old_path(b_path, b_mode)
d_line = LineMapper.new(bd_diff).old_to_new(b_line)
cd_diff = cd_diffs.diff_file_with_old_path(c_path)
cd_diff = cd_diffs.diff_file_with_old_path(c_path, c_mode)
if c_line && d_line
# If the line is still in C and D, it is still unchanged.
......
......@@ -15,28 +15,9 @@ module Gitlab
oid: oid,
limit: limit
)
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_blob, request, timeout: GitalyClient.fast_timeout)
data = []
blob = nil
response.each do |msg|
if blob.nil?
blob = msg
end
data << msg.data
GitalyClient.streaming_call(@gitaly_repo.storage_name, :blob_service, :get_blob, request, timeout: GitalyClient.fast_timeout) do |response|
consume_blob_response(response)
end
return if blob.oid.blank?
data = data.join
Gitlab::Git::Blob.new(
id: blob.oid,
size: blob.size,
data: data,
binary: Gitlab::Git::Blob.binary?(data)
)
end
def batch_lfs_pointers(blob_ids)
......@@ -47,9 +28,9 @@ module Gitlab
blob_ids: blob_ids
)
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request, timeout: GitalyClient.medium_timeout)
map_lfs_pointers(response)
GitalyClient.streaming_call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request, timeout: GitalyClient.medium_timeout) do |response|
map_lfs_pointers(response)
end
end
def get_blobs(revision_paths, limit = -1)
......@@ -65,15 +46,15 @@ module Gitlab
limit: limit
)
response = GitalyClient.call(
GitalyClient.streaming_call(
@gitaly_repo.storage_name,
:blob_service,
:get_blobs,
request,
timeout: GitalyClient.fast_timeout
)
GitalyClient::BlobsStitcher.new(response)
) do |response|
GitalyClient::BlobsStitcher.new(response)
end
end
def get_blob_types(revision_paths, limit = -1)
......@@ -89,15 +70,15 @@ module Gitlab
limit: limit
)
response = GitalyClient.call(
GitalyClient.streaming_call(
@gitaly_repo.storage_name,
:blob_service,
:get_blobs,
request,
timeout: GitalyClient.fast_timeout
)
map_blob_types(response)
) do |response|
map_blob_types(response)
end
end
def get_new_lfs_pointers(revision, limit, not_in, dynamic_timeout = nil)
......@@ -120,15 +101,15 @@ module Gitlab
GitalyClient.medium_timeout
end
response = GitalyClient.call(
GitalyClient.streaming_call(
@gitaly_repo.storage_name,
:blob_service,
:get_new_lfs_pointers,
request,
timeout: timeout
)
map_lfs_pointers(response)
) do |response|
map_lfs_pointers(response)
end
end
def get_all_lfs_pointers
......@@ -136,13 +117,36 @@ module Gitlab
repository: @gitaly_repo
)
response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_all_lfs_pointers, request, timeout: GitalyClient.medium_timeout)
map_lfs_pointers(response)
GitalyClient.streaming_call(@gitaly_repo.storage_name, :blob_service, :get_all_lfs_pointers, request, timeout: GitalyClient.medium_timeout) do |response|
map_lfs_pointers(response)
end
end
private
def consume_blob_response(response)
data = []
blob = nil
response.each do |msg|
if blob.nil?
blob = msg
end
data << msg.data
end
return if blob.oid.blank?
data = data.join
Gitlab::Git::Blob.new(
id: blob.oid,
size: blob.size,
data: data,
binary: Gitlab::Git::Blob.binary?(data)
)
end
def map_lfs_pointers(response)
response.flat_map do |message|
message.lfs_pointers.map do |lfs_pointer|
......
......@@ -20,20 +20,20 @@ module Gitlab
when DashboardProcessingError
error(error.message, :unprocessable_entity)
when NOT_FOUND_ERROR
error("#{dashboard_path} could not be found.", :not_found)
error(_("%{dashboard_path} could not be found.") % { dashboard_path: dashboard_path }, :not_found)
when PanelNotFoundError
error(error.message, :not_found)
when ::Grafana::Client::Error
error(error.message, :service_unavailable)
when MissingIntegrationError
error('Proxy support for this API is not available currently', :bad_request)
error(_('Proxy support for this API is not available currently'), :bad_request)
else
raise error
end
end
def panels_not_found!(opts)
raise PanelNotFoundError.new("No panels matching properties #{opts}")
raise PanelNotFoundError.new(_("No panels matching properties %{opts}") % { opts: opts })
end
end
end
......
......@@ -6,7 +6,7 @@ module Gitlab
module Stages
class MetricEndpointInserter < BaseStage
def transform!
raise Errors::DashboardProcessingError.new('Environment is required for Stages::MetricEndpointInserter') unless params[:environment]
raise Errors::DashboardProcessingError.new(_('Environment is required for Stages::MetricEndpointInserter')) unless params[:environment]
for_metrics do |metric|
metric[:prometheus_endpoint_path] = endpoint_for_metric(metric)
......@@ -33,7 +33,11 @@ module Gitlab
end
def query_type(metric)
metric[:query] ? :query : :query_range
if metric[:query]
::Prometheus::ProxyService::PROMETHEUS_QUERY_API.to_sym
else
::Prometheus::ProxyService::PROMETHEUS_QUERY_RANGE_API.to_sym
end
end
def query_for_metric(metric)
......
......@@ -326,6 +326,9 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
msgid "%{dashboard_path} could not be found."
msgstr ""
msgid "%{days} days until tags are automatically removed"
msgstr ""
......@@ -4669,9 +4672,15 @@ msgstr ""
msgid "Cluster does not exist"
msgstr ""
msgid "Cluster is required for Stages::ClusterEndpointInserter"
msgstr ""
msgid "Cluster level"
msgstr ""
msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter"
msgstr ""
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
......@@ -7070,6 +7079,9 @@ msgstr ""
msgid "Dashboard"
msgstr ""
msgid "Dashboard uid not found"
msgstr ""
msgid "DashboardProjects|All"
msgstr ""
......@@ -7094,6 +7106,9 @@ msgstr ""
msgid "Data is still calculating..."
msgstr ""
msgid "Datasource name not found"
msgstr ""
msgid "Date"
msgstr ""
......@@ -8555,6 +8570,9 @@ msgstr ""
msgid "Environment does not have deployments"
msgstr ""
msgid "Environment is required for Stages::MetricEndpointInserter"
msgstr ""
msgid "Environment is required for Stages::VariableEndpointInserter"
msgstr ""
......@@ -10967,6 +10985,9 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
msgid "Grafana response contains invalid json"
msgstr ""
msgid "GrafanaIntegration|API Token"
msgstr ""
......@@ -11084,6 +11105,9 @@ msgstr ""
msgid "Group info:"
msgstr ""
msgid "Group is required when cluster_type is :group"
msgstr ""
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
......@@ -15068,6 +15092,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
msgid "No panels matching properties %{opts}"
msgstr ""
msgid "No parent group"
msgstr ""
......@@ -15994,6 +16021,9 @@ msgstr ""
msgid "Parent epic is not present."
msgstr ""
msgid "Parsing error for param :embed_json. %{message}"
msgstr ""
msgid "Part of merge request changes"
msgstr ""
......@@ -17299,6 +17329,9 @@ msgstr ""
msgid "Project has too many %{label_for_message} to search"
msgstr ""
msgid "Project is required when cluster_type is :project"
msgstr ""
msgid "Project members"
msgstr ""
......@@ -17416,9 +17449,6 @@ msgstr ""
msgid "ProjectService|Comment"
msgstr ""
msgid "ProjectService|Comment will be posted on each event"
msgstr ""
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
msgstr ""
......@@ -18211,6 +18241,9 @@ msgstr ""
msgid "Provider"
msgstr ""
msgid "Proxy support for this API is not available currently"
msgstr ""
msgid "Pseudonymizer data collection"
msgstr ""
......@@ -24212,6 +24245,9 @@ msgstr ""
msgid "Unreachable"
msgstr ""
msgid "Unrecognized cluster type"
msgstr ""
msgid "Unresolve"
msgstr ""
......
......@@ -262,6 +262,17 @@ RSpec.describe ProjectsFinder, :do_not_mock_admin_mode do
it { is_expected.to match_array([public_project]) }
end
describe 'filter by repository_storage' do
let(:params) { { repository_storage: 'nfs-05' } }
let!(:project) { create(:project, :public) }
before do
project.update_columns(repository_storage: 'nfs-05')
end
it { is_expected.to match_array([project]) }
end
describe 'sorting' do
let(:params) { { sort: 'name_asc' } }
......
......@@ -147,6 +147,20 @@ describe('DynamicField', () => {
.text(),
).toBe(defaultProps.help);
});
it('renders description with help text as HTML', () => {
const helpHTML = 'The <strong>URL</strong> of the project';
createComponent({
help: helpHTML,
});
expect(
findGlFormGroup()
.find('small')
.html(),
).toContain(helpHTML);
});
});
describe('label text', () => {
......
......@@ -7,9 +7,4 @@ describe ServicesHelper do
it { expect(event_action_title('comment')).to eq 'Comment' }
it { expect(event_action_title('something')).to eq 'Something' }
end
describe 'event_action_description' do
it { expect(event_action_description('comment')).to eq 'Comment will be posted on each event' }
it { expect(event_action_description('something')).to eq nil }
end
end
......@@ -234,5 +234,118 @@ describe Gitlab::Diff::PositionTracer::ImageStrategy do
end
end
end
describe 'symlink scenarios' do
let(:new_file) { old_file_status == :new }
let(:deleted_file) { old_file_status == :deleted }
let(:renamed_file) { old_file_status == :renamed }
let(:file_identifier) { "#{file_name}-#{new_file}-#{deleted_file}-#{renamed_file}" }
let(:file_identifier_hash) { Digest::SHA1.hexdigest(file_identifier) }
let(:old_position) { position(old_path: file_name, new_path: file_name, position_type: 'image', file_identifier_hash: file_identifier_hash) }
let(:update_file_commit) do
initial_commit
update_file(
branch_name,
file_name,
Base64.encode64('morecontent')
)
end
let(:delete_file_commit) do
initial_commit
delete_file(branch_name, file_name)
end
let(:create_second_file_commit) do
initial_commit
create_file(
branch_name,
second_file_name,
Base64.encode64('morecontent')
)
end
before do
stub_feature_flags(file_identifier_hash: true)
end
describe 'from symlink to image' do
let(:initial_commit) { project.commit('a19c7f9a147e35e535c797cf148d29c24dac5544') }
let(:symlink_to_image_commit) { project.commit('8cfca8420812e5bd7479aa32cf33e0c95a3ca576') }
let(:branch_name) { 'diff-files-symlink-to-image' }
let(:file_name) { 'symlink-to-image.png' }
context "when the old position is on the new image file" do
let(:old_file_status) { :new }
context "when the image file's content was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_image_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) }
it "returns the new position" do
expect_new_position(
old_path: file_name,
new_path: file_name
)
end
end
context "when the image file's content was changed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_image_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, update_file_commit) }
let(:change_diff_refs) { diff_refs(symlink_to_image_commit, update_file_commit) }
it "returns the position of the change" do
expect_change_position(
old_path: file_name,
new_path: file_name
)
end
end
context "when the image file was removed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_image_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, delete_file_commit) }
let(:change_diff_refs) { diff_refs(symlink_to_image_commit, delete_file_commit) }
it "returns the position of the change" do
expect_change_position(
old_path: file_name,
new_path: file_name
)
end
end
end
end
describe 'from image to symlink' do
let(:initial_commit) { project.commit('d10dcdfbbb2b59a959a5f5d66a4adf28f0ea4008') }
let(:image_to_symlink_commit) { project.commit('3e94fdaa60da8aed38401b91bc56be70d54ca424') }
let(:branch_name) { 'diff-files-image-to-symlink' }
let(:file_name) { 'image-to-symlink.png' }
context "when the old position is on the added image file" do
let(:old_file_status) { :new }
context "when the image file gets changed to a symlink between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit.parent, initial_commit) }
let(:new_diff_refs) { diff_refs(initial_commit.parent, image_to_symlink_commit) }
let(:change_diff_refs) { diff_refs(initial_commit, image_to_symlink_commit) }
it "returns the position of the change" do
expect_change_position(
old_path: file_name,
new_path: file_name
)
end
end
end
end
end
end
end
......@@ -1801,5 +1801,143 @@ describe Gitlab::Diff::PositionTracer::LineStrategy do
end
end
end
describe 'symlink scenarios' do
let(:new_file) { old_file_status == :new }
let(:deleted_file) { old_file_status == :deleted }
let(:renamed_file) { old_file_status == :renamed }
let(:file_identifier) { "#{file_name}-#{new_file}-#{deleted_file}-#{renamed_file}" }
let(:file_identifier_hash) { Digest::SHA1.hexdigest(file_identifier) }
let(:update_line_commit) do
update_file(
branch_name,
file_name,
<<-CONTENT.strip_heredoc
A
BB
C
CONTENT
)
end
let(:delete_file_commit) do
delete_file(branch_name, file_name)
end
let(:create_second_file_commit) do
create_file(
branch_name,
second_file_name,
<<-CONTENT.strip_heredoc
D
E
CONTENT
)
end
before do
stub_feature_flags(file_identifier_hash: true)
end
describe 'from symlink to text' do
let(:initial_commit) { project.commit('0e5b363105e9176a77bac94d7ff6d8c4fb35c3eb') }
let(:symlink_to_text_commit) { project.commit('689815e617abc6889f1fded4834d2dd7d942a58e') }
let(:branch_name) { 'diff-files-symlink-to-text' }
let(:file_name) { 'symlink-to-text.txt' }
let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 3, file_identifier_hash: file_identifier_hash) }
before do
create_branch('diff-files-symlink-to-text-test', branch_name)
end
context "when the old position is on the new text file" do
let(:old_file_status) { :new }
context "when the text file's content was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, create_second_file_commit) }
it "returns the new position" do
expect_new_position(
new_path: old_position.new_path,
new_line: old_position.new_line
)
end
end
context "when the text file's content has change, but the line was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
it "returns the new position" do
expect_new_position(
new_path: old_position.new_path,
new_line: old_position.new_line
)
end
end
context "when the text file's line was changed between the old and the new diff" do
let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 2, file_identifier_hash: file_identifier_hash) }
let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
let(:change_diff_refs) { diff_refs(symlink_to_text_commit, update_line_commit) }
it "returns the position of the change" do
expect_change_position(
old_path: file_name,
new_path: file_name,
old_line: 2,
new_line: nil
)
end
end
context "when the text file was removed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, symlink_to_text_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, delete_file_commit) }
let(:change_diff_refs) { diff_refs(symlink_to_text_commit, delete_file_commit) }
it "returns the position of the change" do
expect_change_position(
old_path: file_name,
new_path: file_name,
old_line: 3,
new_line: nil
)
end
end
end
describe 'from text to symlink' do
let(:initial_commit) { project.commit('3db7bd90bab8ce8f02c9818590b84739a2e97230') }
let(:text_to_symlink_commit) { project.commit('5e2c2708c2e403dece5dd25759369150aac51644') }
let(:branch_name) { 'diff-files-text-to-symlink' }
let(:file_name) { 'text-to-symlink.txt' }
context "when the position is on the added text file" do
let(:old_file_status) { :new }
context "when the text file gets changed to a symlink between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit.parent, initial_commit) }
let(:new_diff_refs) { diff_refs(initial_commit.parent, text_to_symlink_commit) }
let(:change_diff_refs) { diff_refs(initial_commit, text_to_symlink_commit) }
it "returns the position of the change" do
expect_change_position(
old_path: file_name,
new_path: file_name,
old_line: 3,
new_line: nil
)
end
end
end
end
end
end
end
end
......@@ -47,8 +47,8 @@ describe Ci::Bridge do
CI_JOB_NAME CI_JOB_STAGE CI_COMMIT_SHA CI_COMMIT_SHORT_SHA
CI_COMMIT_BEFORE_SHA CI_COMMIT_REF_NAME CI_COMMIT_REF_SLUG
CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH
CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PIPELINE_IID
CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE
CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PROJECT_ROOT_NAMESPACE
CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE
CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED
]
......
......@@ -2380,6 +2380,7 @@ describe Ci::Build do
{ key: 'CI_PROJECT_PATH', value: project.full_path, public: true, masked: false },
{ key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true, masked: false },
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true, masked: false },
{ key: 'CI_PROJECT_ROOT_NAMESPACE', value: project.namespace.root_ancestor.path, public: true, masked: false },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true, masked: false },
{ key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false },
{ key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: project.repository_languages.map(&:name).join(',').downcase, public: true, masked: false },
......
......@@ -130,6 +130,24 @@ describe GlobalPolicy do
end
end
describe 'using project statistics filters' do
context 'regular user' do
it { is_expected.not_to be_allowed(:use_project_statistics_filters) }
end
context 'admin' do
let(:current_user) { create(:user, :admin) }
context 'when admin mode is enabled', :enable_admin_mode do
it { is_expected.to be_allowed(:use_project_statistics_filters) }
end
context 'when admin mode is disabled' do
it { is_expected.to be_disallowed(:use_project_statistics_filters) }
end
end
end
shared_examples 'access allowed when terms accepted' do |ability|
it { is_expected.not_to be_allowed(ability) }
......
......@@ -584,6 +584,85 @@ describe API::Projects do
end
end
context 'sorting by project statistics' do
%w(repository_size storage_size wiki_size).each do |order_by|
context "sorting by #{order_by}" do
before do
ProjectStatistics.update_all(order_by => 100)
project4.statistics.update_columns(order_by => 10)
project.statistics.update_columns(order_by => 200)
end
context 'admin user' do
let(:current_user) { admin }
context "when sorting by #{order_by} ascendingly" do
it 'returns a properly sorted list of projects' do
get api('/projects', current_user), params: { order_by: order_by, sort: :asc }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(project4.id)
end
end
context "when sorting by #{order_by} descendingly" do
it 'returns a properly sorted list of projects' do
get api('/projects', current_user), params: { order_by: order_by, sort: :desc }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(project.id)
end
end
end
context 'non-admin user' do
let(:current_user) { user }
let(:projects) { [public_project, project, project2, project3] }
it 'returns projects ordered normally' do
get api('/projects', current_user), params: { order_by: order_by }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |project| project['id'] }).to eq(projects.map(&:id).reverse)
end
end
end
end
end
context 'filtering by repository_storage' do
before do
[project, project3].each { |proj| proj.update_columns(repository_storage: 'nfs-11') }
# Since we don't actually have Gitaly configured with an nfs-11 storage, an error would be raised
# when we present the projects in a response, as we ask Gitaly for stuff like default branch and Gitaly
# is not configured for a nfs-11 storage. So we trick Rails into thinking the storage for these projects
# is still default (in reality, it is).
allow_any_instance_of(Project).to receive(:repository_storage).and_return('default')
end
context 'admin user' do
it_behaves_like 'projects response' do
let(:filter) { { repository_storage: 'nfs-11' } }
let(:current_user) { admin }
let(:projects) { [project, project3] }
end
end
context 'non-admin user' do
it_behaves_like 'projects response' do
let(:filter) { { repository_storage: 'nfs-11' } }
let(:current_user) { user }
let(:projects) { [public_project, project, project2, project3] }
end
end
end
context 'with keyset pagination' do
let(:current_user) { user }
let(:projects) { [public_project, project, project2, project3] }
......
......@@ -29,6 +29,10 @@ module TestEnv
'gitattributes' => '5a62481',
'expand-collapse-diffs' => '4842455',
'symlink-expand-diff' => '81e6355',
'diff-files-symlink-to-image' => '8cfca84',
'diff-files-image-to-symlink' => '3e94fda',
'diff-files-symlink-to-text' => '689815e',
'diff-files-text-to-symlink' => '5e2c270',
'expand-collapse-files' => '025db92',
'expand-collapse-lines' => '238e82d',
'pages-deploy' => '7897d5b',
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册