Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ce493944f4
commit
60082b335c
|
@ -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
|
||||
return [] unless integration_form_refactor?
|
||||
|
||||
ServiceEventSerializer.new(service: @service).represent(@service.configurable_events).to_json
|
||||
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 fields_for_service
|
||||
def trigger_events_for_service(integration)
|
||||
return [] unless integration_form_refactor?
|
||||
|
||||
ServiceFieldSerializer.new(service: @service).represent(@service.global_fields).to_json
|
||||
ServiceEventSerializer.new(service: integration).represent(integration.configurable_events).to_json
|
||||
end
|
||||
|
||||
def show_service_trigger_events?
|
||||
return false if @service.is_a?(JiraService) || integration_form_refactor?
|
||||
def fields_for_service(integration)
|
||||
return [] unless integration_form_refactor?
|
||||
|
||||
@service.configurable_events.present?
|
||||
ServiceFieldSerializer.new(service: integration).represent(integration.global_fields).to_json
|
||||
end
|
||||
|
||||
def show_service_trigger_events?(integration)
|
||||
return false if integration.is_a?(JiraService) || integration_form_refactor?
|
||||
|
||||
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'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
- 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
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
= 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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
- 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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Refine UI of integration form
|
||||
merge_request: 34707
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add CI_PROJECT_ROOT_NAMESPACE predefined environment variable
|
||||
merge_request: 34733
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow advanced API projects filtering for admins
|
||||
merge_request: 32879
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Gitaly duration timings of BlobService RPCs
|
||||
merge_request: 34906
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,17 @@
|
|||
# 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
|
|
@ -0,0 +1,17 @@
|
|||
# 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
|
|
@ -0,0 +1,17 @@
|
|||
# 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',
|
||||
|
|
Loading…
Reference in New Issue