Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-27 09:10:32 +00:00
parent 03c84e0de5
commit f0aaec1f67
24 changed files with 211 additions and 184 deletions

View File

@ -1650,14 +1650,11 @@ Gitlab/NamespacedClass:
- 'app/models/project_services/chat_notification_service.rb'
- 'app/models/project_services/discord_service.rb'
- 'app/models/project_services/hangouts_chat_service.rb'
- 'app/models/project_services/issue_tracker_data.rb'
- 'app/models/project_services/jira_tracker_data.rb'
- 'app/models/project_services/mattermost_service.rb'
- 'app/models/project_services/mattermost_slash_commands_service.rb'
- 'app/models/project_services/microsoft_teams_service.rb'
- 'app/models/project_services/mock_monitoring_service.rb'
- 'app/models/project_services/monitoring_service.rb'
- 'app/models/project_services/open_project_tracker_data.rb'
- 'app/models/project_services/prometheus_service.rb'
- 'app/models/project_services/pushover_service.rb'
- 'app/models/project_services/slack_service.rb'
@ -2675,8 +2672,8 @@ Gitlab/DelegatePredicateMethods:
- 'app/models/clusters/platforms/kubernetes.rb'
- 'app/models/concerns/ci/metadatable.rb'
- 'app/models/concerns/diff_positionable_note.rb'
- 'app/models/concerns/integrations/base_data_fields.rb'
- 'app/models/concerns/resolvable_discussion.rb'
- 'app/models/concerns/services/data_fields.rb'
- 'app/models/project.rb'
- 'ee/app/models/concerns/ee/ci/metadatable.rb'
- 'ee/app/models/ee/group.rb'

View File

@ -1 +1 @@
c93530dd0922e7554d6d1e486e830f72980fe083
6e58da454633a53c86d14a59587ee7a6a9d11031

View File

@ -142,7 +142,9 @@ export default {
text: s__('ForkProject|Private'),
value: PRIVATE_VISIBILITY,
icon: 'lock',
help: s__('ForkProject|The project can be accessed without any authentication.'),
help: s__(
'ForkProject|Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.',
),
disabled: this.isVisibilityLevelDisabled(PRIVATE_VISIBILITY),
},
{
@ -156,9 +158,7 @@ export default {
text: s__('ForkProject|Public'),
value: PUBLIC_VISIBILITY,
icon: 'earth',
help: s__(
'ForkProject|Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.',
),
help: s__('ForkProject|The project can be accessed without any authentication.'),
disabled: this.isVisibilityLevelDisabled(PUBLIC_VISIBILITY),
},
];

View File

@ -1,11 +1,13 @@
# frozen_string_literal: true
module Services
module DataFields
module Integrations
module BaseDataFields
extend ActiveSupport::Concern
included do
belongs_to :integration, inverse_of: self.name.underscore.to_sym, foreign_key: :service_id
# TODO: Once we rename the tables we can't rely on `table_name` anymore.
# https://gitlab.com/gitlab-org/gitlab/-/issues/331953
belongs_to :integration, inverse_of: self.table_name.to_sym, foreign_key: :service_id
delegate :activated?, to: :integration, allow_nil: true

View File

@ -0,0 +1,61 @@
# frozen_string_literal: true
module Integrations
module HasDataFields
extend ActiveSupport::Concern
class_methods do
# Provide convenient accessor methods for data fields.
# TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
def data_field(*args)
args.each do |arg|
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
unless method_defined?(arg)
def #{arg}
data_fields.send('#{arg}') || (properties && properties['#{arg}'])
end
end
def #{arg}=(value)
@old_data_fields ||= {}
@old_data_fields['#{arg}'] ||= #{arg} # set only on the first assignment, IOW we remember the original value only
data_fields.send('#{arg}=', value)
end
def #{arg}_touched?
@old_data_fields ||= {}
@old_data_fields.has_key?('#{arg}')
end
def #{arg}_changed?
#{arg}_touched? && @old_data_fields['#{arg}'] != #{arg}
end
def #{arg}_was
return unless #{arg}_touched?
return if data_fields.persisted? # arg_was does not work for attr_encrypted
legacy_properties_data['#{arg}']
end
RUBY
end
end
end
included do
has_one :issue_tracker_data, autosave: true, inverse_of: :integration, foreign_key: :service_id, class_name: 'Integrations::IssueTrackerData'
has_one :jira_tracker_data, autosave: true, inverse_of: :integration, foreign_key: :service_id, class_name: 'Integrations::JiraTrackerData'
has_one :open_project_tracker_data, autosave: true, inverse_of: :integration, foreign_key: :service_id, class_name: 'Integrations::OpenProjectTrackerData'
def data_fields
raise NotImplementedError
end
def data_fields_present?
data_fields.present?
rescue NotImplementedError
false
end
end
end
end

View File

@ -6,7 +6,7 @@ class Integration < ApplicationRecord
include Sortable
include Importable
include ProjectServicesLoggable
include DataFields
include Integrations::HasDataFields
include FromUnion
include EachBatch

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
module Integrations
class IssueTrackerData < ApplicationRecord
include BaseDataFields
attr_encrypted :project_url, encryption_options
attr_encrypted :issues_url, encryption_options
attr_encrypted :new_issue_url, encryption_options
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Integrations
class JiraTrackerData < ApplicationRecord
include BaseDataFields
attr_encrypted :url, encryption_options
attr_encrypted :api_url, encryption_options
attr_encrypted :username, encryption_options
attr_encrypted :password, encryption_options
enum deployment_type: { unknown: 0, server: 1, cloud: 2 }, _prefix: :deployment
end
end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Integrations
class OpenProjectTrackerData < ApplicationRecord
include BaseDataFields
# When the Open Project is fresh installed, the default closed status id is "13" based on current version: v8.
DEFAULT_CLOSED_STATUS_ID = "13"
attr_encrypted :url, encryption_options
attr_encrypted :api_url, encryption_options
attr_encrypted :token, encryption_options
def closed_status_id
super || DEFAULT_CLOSED_STATUS_ID
end
end
end

View File

@ -1,59 +0,0 @@
# frozen_string_literal: true
module DataFields
extend ActiveSupport::Concern
class_methods do
# Provide convenient accessor methods for data fields.
# TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
def data_field(*args)
args.each do |arg|
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
unless method_defined?(arg)
def #{arg}
data_fields.send('#{arg}') || (properties && properties['#{arg}'])
end
end
def #{arg}=(value)
@old_data_fields ||= {}
@old_data_fields['#{arg}'] ||= #{arg} # set only on the first assignment, IOW we remember the original value only
data_fields.send('#{arg}=', value)
end
def #{arg}_touched?
@old_data_fields ||= {}
@old_data_fields.has_key?('#{arg}')
end
def #{arg}_changed?
#{arg}_touched? && @old_data_fields['#{arg}'] != #{arg}
end
def #{arg}_was
return unless #{arg}_touched?
return if data_fields.persisted? # arg_was does not work for attr_encrypted
legacy_properties_data['#{arg}']
end
RUBY
end
end
end
included do
has_one :issue_tracker_data, autosave: true, inverse_of: :integration, foreign_key: :service_id
has_one :jira_tracker_data, autosave: true, inverse_of: :integration, foreign_key: :service_id
has_one :open_project_tracker_data, autosave: true, inverse_of: :integration, foreign_key: :service_id
def data_fields
raise NotImplementedError
end
def data_fields_present?
data_fields.present?
rescue NotImplementedError
false
end
end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class IssueTrackerData < ApplicationRecord
include Services::DataFields
attr_encrypted :project_url, encryption_options
attr_encrypted :issues_url, encryption_options
attr_encrypted :new_issue_url, encryption_options
end

View File

@ -1,12 +0,0 @@
# frozen_string_literal: true
class JiraTrackerData < ApplicationRecord
include Services::DataFields
attr_encrypted :url, encryption_options
attr_encrypted :api_url, encryption_options
attr_encrypted :username, encryption_options
attr_encrypted :password, encryption_options
enum deployment_type: { unknown: 0, server: 1, cloud: 2 }, _prefix: :deployment
end

View File

@ -1,16 +0,0 @@
# frozen_string_literal: true
class OpenProjectTrackerData < ApplicationRecord
include Services::DataFields
# When the Open Project is fresh installed, the default closed status id is "13" based on current version: v8.
DEFAULT_CLOSED_STATUS_ID = "13"
attr_encrypted :url, encryption_options
attr_encrypted :api_url, encryption_options
attr_encrypted :token, encryption_options
def closed_status_id
super || DEFAULT_CLOSED_STATUS_ID
end
end

View File

@ -39,10 +39,10 @@ class Release < ApplicationRecord
scope :released_within_2hrs, -> { where(released_at: Time.zone.now - 1.hour..Time.zone.now + 1.hour) }
# Sorting
scope :order_created, -> { reorder('created_at ASC') }
scope :order_created_desc, -> { reorder('created_at DESC') }
scope :order_released, -> { reorder('released_at ASC') }
scope :order_released_desc, -> { reorder('released_at DESC') }
scope :order_created, -> { reorder(created_at: :asc) }
scope :order_created_desc, -> { reorder(created_at: :desc) }
scope :order_released, -> { reorder(released_at: :asc) }
scope :order_released_desc, -> { reorder(released_at: :desc) }
delegate :repository, to: :project

View File

@ -11,7 +11,7 @@ GitLab stores [repositories](../user/project/repository/index.md) on repository
storage is either:
- A `gitaly_address`, which points to a [Gitaly node](gitaly/index.md).
- A `path`, which points directly a directory where the repositories are stored. This method is
- A `path`, which points directly to the directory where the repositories are stored. This method is
deprecated and [scheduled to be removed](https://gitlab.com/gitlab-org/gitaly/-/issues/1690) in
GitLab 14.0.

View File

@ -140,10 +140,10 @@ namespace:
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/main/download?job=coverage
```
To download the file `coverage/index.html` from the same artifacts:
To download the file `review/index.html` from the same artifacts:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/master/raw/coverage/index.html?job=coverage
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/main/raw/review/index.html?job=coverage
```
To browse the latest job artifacts:
@ -155,7 +155,7 @@ https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/browse?job=<job
For example:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/master/browse?job=coverage
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/main/browse?job=coverage
```
To download specific files, including HTML files that
@ -168,7 +168,7 @@ https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/file/<path>?job
For example, when a job `coverage` creates the artifact `htmlcov/index.html`:
```plaintext
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/master/file/htmlcov/index.html?job=coverage
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/main/file/htmlcov/index.html?job=coverage
```
## When job artifacts are deleted

View File

@ -3,15 +3,15 @@
# These factories should not be called directly unless we are testing a _tracker_data model.
# The factories are used when creating integrations.
FactoryBot.define do
factory :jira_tracker_data do
factory :jira_tracker_data, class: 'Integrations::JiraTrackerData' do
integration factory: :jira_service
end
factory :issue_tracker_data do
factory :issue_tracker_data, class: 'Integrations::IssueTrackerData' do
integration
end
factory :open_project_tracker_data do
factory :open_project_tracker_data, class: 'Integrations::OpenProjectTrackerData' do
integration factory: :open_project_service
url { 'http://openproject.example.com'}
token { 'supersecret' }

View File

@ -1,4 +1,4 @@
import { GlFormInputGroup, GlFormInput, GlForm } from '@gitlab/ui';
import { GlFormInputGroup, GlFormInput, GlForm, GlFormRadio } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
@ -15,6 +15,13 @@ describe('ForkForm component', () => {
let wrapper;
let axiosMock;
const PROJECT_VISIBILITY_TYPE = {
private:
'Private Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.',
internal: 'Internal The project can be accessed by any logged in user.',
public: 'Public The project can be accessed without any authentication.',
};
const GON_GITLAB_URL = 'https://gitlab.com';
const GON_API_VERSION = 'v7';
@ -61,6 +68,7 @@ describe('ForkForm component', () => {
stubs: {
GlFormInputGroup,
GlFormInput,
GlFormRadio,
},
});
};
@ -203,6 +211,24 @@ describe('ForkForm component', () => {
});
describe('visibility level', () => {
it('displays the correct description', () => {
mockGetRequest();
createComponent();
const formRadios = wrapper.findAll(GlFormRadio);
Object.keys(PROJECT_VISIBILITY_TYPE).forEach((visibilityType, index) => {
expect(formRadios.at(index).text()).toBe(PROJECT_VISIBILITY_TYPE[visibilityType]);
});
});
it('displays all 3 visibility levels', () => {
mockGetRequest();
createComponent();
expect(wrapper.findAll(GlFormRadio)).toHaveLength(3);
});
it.each`
project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'}

View File

@ -121,10 +121,10 @@ Object {
},
],
"paginationInfo": Object {
"endCursor": "eyJpZCI6IjEifQ",
"endCursor": "eyJyZWxlYXNlZF9hdCI6IjIwMTgtMTItMTAgMDA6MDA6MDAuMDAwMDAwMDAwIFVUQyIsImlkIjoiMSJ9",
"hasNextPage": false,
"hasPreviousPage": false,
"startCursor": "eyJpZCI6IjEifQ",
"startCursor": "eyJyZWxlYXNlZF9hdCI6IjIwMTgtMTItMTAgMDA6MDA6MDAuMDAwMDAwMDAwIFVUQyIsImlkIjoiMSJ9",
},
}
`;

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe DataFields do
RSpec.describe Integrations::HasDataFields do
let(:url) { 'http://url.com' }
let(:username) { 'username_one' }
let(:properties) do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe IssueTrackerData do
RSpec.describe Integrations::IssueTrackerData do
describe 'associations' do
it { is_expected.to belong_to :integration }
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe JiraTrackerData do
RSpec.describe Integrations::JiraTrackerData do
describe 'associations' do
it { is_expected.to belong_to(:integration) }
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe OpenProjectTrackerData do
RSpec.describe Integrations::OpenProjectTrackerData do
describe 'associations' do
it { is_expected.to belong_to(:integration) }
end

View File

@ -295,75 +295,69 @@ RSpec.describe 'Query.project(fullPath).releases()' do
end
end
describe 'sorting behavior' do
let_it_be(:today) { Time.now }
let_it_be(:yesterday) { today - 1.day }
let_it_be(:tomorrow) { today + 1.day }
describe 'sorting and pagination' do
let_it_be(:sort_project) { create(:project, :public) }
let_it_be(:project) { create(:project, :repository, :public) }
let(:data_path) { [:project, :releases] }
let(:current_user) { developer }
let_it_be(:release_v1) { create(:release, project: project, tag: 'v1', released_at: yesterday, created_at: tomorrow) }
let_it_be(:release_v2) { create(:release, project: project, tag: 'v2', released_at: today, created_at: yesterday) }
let_it_be(:release_v3) { create(:release, project: project, tag: 'v3', released_at: tomorrow, created_at: today) }
let(:current_user) { developer }
let(:params) { nil }
let(:sorted_tags) do
graphql_data.dig('project', 'releases', 'nodes').map { |release| release['tagName'] }
def pagination_query(params)
graphql_query_for(
:project,
{ full_path: sort_project.full_path },
query_graphql_field(:releases, params, "#{page_info} nodes { tagName }")
)
end
let(:query) do
graphql_query_for(:project, { fullPath: project.full_path },
%{
releases#{params ? "(#{params})" : ""} {
nodes {
tagName
}
}
})
def pagination_results_data(nodes)
nodes.map { |release| release['tagName'] }
end
before do
post_query
end
context 'when sorting by released_at' do
let_it_be(:release5) { create(:release, project: sort_project, tag: 'v5.5.0', released_at: 3.days.from_now) }
let_it_be(:release1) { create(:release, project: sort_project, tag: 'v5.1.0', released_at: 3.days.ago) }
let_it_be(:release4) { create(:release, project: sort_project, tag: 'v5.4.0', released_at: 2.days.from_now) }
let_it_be(:release2) { create(:release, project: sort_project, tag: 'v5.2.0', released_at: 2.days.ago) }
let_it_be(:release3) { create(:release, project: sort_project, tag: 'v5.3.0', released_at: 1.day.ago) }
context 'when no sort: parameter is provided' do
it 'returns the results with the default sort applied (sort: RELEASED_AT_DESC)' do
expect(sorted_tags).to eq(%w(v3 v2 v1))
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :RELEASED_AT_ASC }
let(:first_param) { 2 }
let(:expected_results) { [release1.tag, release2.tag, release3.tag, release4.tag, release5.tag] }
end
end
context 'when descending' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :RELEASED_AT_DESC }
let(:first_param) { 2 }
let(:expected_results) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] }
end
end
end
context 'with sort: RELEASED_AT_DESC' do
let(:params) { 'sort: RELEASED_AT_DESC' }
context 'when sorting by created_at' do
let_it_be(:release5) { create(:release, project: sort_project, tag: 'v5.5.0', created_at: 3.days.from_now) }
let_it_be(:release1) { create(:release, project: sort_project, tag: 'v5.1.0', created_at: 3.days.ago) }
let_it_be(:release4) { create(:release, project: sort_project, tag: 'v5.4.0', created_at: 2.days.from_now) }
let_it_be(:release2) { create(:release, project: sort_project, tag: 'v5.2.0', created_at: 2.days.ago) }
let_it_be(:release3) { create(:release, project: sort_project, tag: 'v5.3.0', created_at: 1.day.ago) }
it 'returns the releases ordered by released_at in descending order' do
expect(sorted_tags).to eq(%w(v3 v2 v1))
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :CREATED_ASC }
let(:first_param) { 2 }
let(:expected_results) { [release1.tag, release2.tag, release3.tag, release4.tag, release5.tag] }
end
end
end
context 'with sort: RELEASED_AT_ASC' do
let(:params) { 'sort: RELEASED_AT_ASC' }
it 'returns the releases ordered by released_at in ascending order' do
expect(sorted_tags).to eq(%w(v1 v2 v3))
end
end
context 'with sort: CREATED_DESC' do
let(:params) { 'sort: CREATED_DESC' }
it 'returns the releases ordered by created_at in descending order' do
expect(sorted_tags).to eq(%w(v1 v3 v2))
end
end
context 'with sort: CREATED_ASC' do
let(:params) { 'sort: CREATED_ASC' }
it 'returns the releases ordered by created_at in ascending order' do
expect(sorted_tags).to eq(%w(v2 v3 v1))
context 'when descending' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :CREATED_DESC }
let(:first_param) { 2 }
let(:expected_results) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] }
end
end
end
end