Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
03c84e0de5
commit
f0aaec1f67
|
@ -1650,14 +1650,11 @@ Gitlab/NamespacedClass:
|
||||||
- 'app/models/project_services/chat_notification_service.rb'
|
- 'app/models/project_services/chat_notification_service.rb'
|
||||||
- 'app/models/project_services/discord_service.rb'
|
- 'app/models/project_services/discord_service.rb'
|
||||||
- 'app/models/project_services/hangouts_chat_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_service.rb'
|
||||||
- 'app/models/project_services/mattermost_slash_commands_service.rb'
|
- 'app/models/project_services/mattermost_slash_commands_service.rb'
|
||||||
- 'app/models/project_services/microsoft_teams_service.rb'
|
- 'app/models/project_services/microsoft_teams_service.rb'
|
||||||
- 'app/models/project_services/mock_monitoring_service.rb'
|
- 'app/models/project_services/mock_monitoring_service.rb'
|
||||||
- 'app/models/project_services/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/prometheus_service.rb'
|
||||||
- 'app/models/project_services/pushover_service.rb'
|
- 'app/models/project_services/pushover_service.rb'
|
||||||
- 'app/models/project_services/slack_service.rb'
|
- 'app/models/project_services/slack_service.rb'
|
||||||
|
@ -2675,8 +2672,8 @@ Gitlab/DelegatePredicateMethods:
|
||||||
- 'app/models/clusters/platforms/kubernetes.rb'
|
- 'app/models/clusters/platforms/kubernetes.rb'
|
||||||
- 'app/models/concerns/ci/metadatable.rb'
|
- 'app/models/concerns/ci/metadatable.rb'
|
||||||
- 'app/models/concerns/diff_positionable_note.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/resolvable_discussion.rb'
|
||||||
- 'app/models/concerns/services/data_fields.rb'
|
|
||||||
- 'app/models/project.rb'
|
- 'app/models/project.rb'
|
||||||
- 'ee/app/models/concerns/ee/ci/metadatable.rb'
|
- 'ee/app/models/concerns/ee/ci/metadatable.rb'
|
||||||
- 'ee/app/models/ee/group.rb'
|
- 'ee/app/models/ee/group.rb'
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
c93530dd0922e7554d6d1e486e830f72980fe083
|
6e58da454633a53c86d14a59587ee7a6a9d11031
|
||||||
|
|
|
@ -142,7 +142,9 @@ export default {
|
||||||
text: s__('ForkProject|Private'),
|
text: s__('ForkProject|Private'),
|
||||||
value: PRIVATE_VISIBILITY,
|
value: PRIVATE_VISIBILITY,
|
||||||
icon: 'lock',
|
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),
|
disabled: this.isVisibilityLevelDisabled(PRIVATE_VISIBILITY),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -156,9 +158,7 @@ export default {
|
||||||
text: s__('ForkProject|Public'),
|
text: s__('ForkProject|Public'),
|
||||||
value: PUBLIC_VISIBILITY,
|
value: PUBLIC_VISIBILITY,
|
||||||
icon: 'earth',
|
icon: 'earth',
|
||||||
help: s__(
|
help: s__('ForkProject|The project can be accessed without any authentication.'),
|
||||||
'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(PUBLIC_VISIBILITY),
|
disabled: this.isVisibilityLevelDisabled(PUBLIC_VISIBILITY),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Services
|
module Integrations
|
||||||
module DataFields
|
module BaseDataFields
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
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
|
delegate :activated?, to: :integration, allow_nil: true
|
||||||
|
|
|
@ -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
|
|
@ -6,7 +6,7 @@ class Integration < ApplicationRecord
|
||||||
include Sortable
|
include Sortable
|
||||||
include Importable
|
include Importable
|
||||||
include ProjectServicesLoggable
|
include ProjectServicesLoggable
|
||||||
include DataFields
|
include Integrations::HasDataFields
|
||||||
include FromUnion
|
include FromUnion
|
||||||
include EachBatch
|
include EachBatch
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -39,10 +39,10 @@ class Release < ApplicationRecord
|
||||||
scope :released_within_2hrs, -> { where(released_at: Time.zone.now - 1.hour..Time.zone.now + 1.hour) }
|
scope :released_within_2hrs, -> { where(released_at: Time.zone.now - 1.hour..Time.zone.now + 1.hour) }
|
||||||
|
|
||||||
# Sorting
|
# Sorting
|
||||||
scope :order_created, -> { reorder('created_at ASC') }
|
scope :order_created, -> { reorder(created_at: :asc) }
|
||||||
scope :order_created_desc, -> { reorder('created_at DESC') }
|
scope :order_created_desc, -> { reorder(created_at: :desc) }
|
||||||
scope :order_released, -> { reorder('released_at ASC') }
|
scope :order_released, -> { reorder(released_at: :asc) }
|
||||||
scope :order_released_desc, -> { reorder('released_at DESC') }
|
scope :order_released_desc, -> { reorder(released_at: :desc) }
|
||||||
|
|
||||||
delegate :repository, to: :project
|
delegate :repository, to: :project
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ GitLab stores [repositories](../user/project/repository/index.md) on repository
|
||||||
storage is either:
|
storage is either:
|
||||||
|
|
||||||
- A `gitaly_address`, which points to a [Gitaly node](gitaly/index.md).
|
- 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
|
deprecated and [scheduled to be removed](https://gitlab.com/gitlab-org/gitaly/-/issues/1690) in
|
||||||
GitLab 14.0.
|
GitLab 14.0.
|
||||||
|
|
||||||
|
|
|
@ -140,10 +140,10 @@ namespace:
|
||||||
https://gitlab.com/gitlab-org/gitlab/-/jobs/artifacts/main/download?job=coverage
|
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
|
```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:
|
To browse the latest job artifacts:
|
||||||
|
@ -155,7 +155,7 @@ https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/browse?job=<job
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```plaintext
|
```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
|
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`:
|
For example, when a job `coverage` creates the artifact `htmlcov/index.html`:
|
||||||
|
|
||||||
```plaintext
|
```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
|
## When job artifacts are deleted
|
||||||
|
|
|
@ -3,15 +3,15 @@
|
||||||
# These factories should not be called directly unless we are testing a _tracker_data model.
|
# These factories should not be called directly unless we are testing a _tracker_data model.
|
||||||
# The factories are used when creating integrations.
|
# The factories are used when creating integrations.
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :jira_tracker_data do
|
factory :jira_tracker_data, class: 'Integrations::JiraTrackerData' do
|
||||||
integration factory: :jira_service
|
integration factory: :jira_service
|
||||||
end
|
end
|
||||||
|
|
||||||
factory :issue_tracker_data do
|
factory :issue_tracker_data, class: 'Integrations::IssueTrackerData' do
|
||||||
integration
|
integration
|
||||||
end
|
end
|
||||||
|
|
||||||
factory :open_project_tracker_data do
|
factory :open_project_tracker_data, class: 'Integrations::OpenProjectTrackerData' do
|
||||||
integration factory: :open_project_service
|
integration factory: :open_project_service
|
||||||
url { 'http://openproject.example.com'}
|
url { 'http://openproject.example.com'}
|
||||||
token { 'supersecret' }
|
token { 'supersecret' }
|
||||||
|
|
|
@ -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 { mount, shallowMount } from '@vue/test-utils';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||||
|
@ -15,6 +15,13 @@ describe('ForkForm component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let axiosMock;
|
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_GITLAB_URL = 'https://gitlab.com';
|
||||||
const GON_API_VERSION = 'v7';
|
const GON_API_VERSION = 'v7';
|
||||||
|
|
||||||
|
@ -61,6 +68,7 @@ describe('ForkForm component', () => {
|
||||||
stubs: {
|
stubs: {
|
||||||
GlFormInputGroup,
|
GlFormInputGroup,
|
||||||
GlFormInput,
|
GlFormInput,
|
||||||
|
GlFormRadio,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -203,6 +211,24 @@ describe('ForkForm component', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('visibility level', () => {
|
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`
|
it.each`
|
||||||
project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled
|
project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled
|
||||||
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'}
|
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'}
|
||||||
|
|
|
@ -121,10 +121,10 @@ Object {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"paginationInfo": Object {
|
"paginationInfo": Object {
|
||||||
"endCursor": "eyJpZCI6IjEifQ",
|
"endCursor": "eyJyZWxlYXNlZF9hdCI6IjIwMTgtMTItMTAgMDA6MDA6MDAuMDAwMDAwMDAwIFVUQyIsImlkIjoiMSJ9",
|
||||||
"hasNextPage": false,
|
"hasNextPage": false,
|
||||||
"hasPreviousPage": false,
|
"hasPreviousPage": false,
|
||||||
"startCursor": "eyJpZCI6IjEifQ",
|
"startCursor": "eyJyZWxlYXNlZF9hdCI6IjIwMTgtMTItMTAgMDA6MDA6MDAuMDAwMDAwMDAwIFVUQyIsImlkIjoiMSJ9",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe DataFields do
|
RSpec.describe Integrations::HasDataFields do
|
||||||
let(:url) { 'http://url.com' }
|
let(:url) { 'http://url.com' }
|
||||||
let(:username) { 'username_one' }
|
let(:username) { 'username_one' }
|
||||||
let(:properties) do
|
let(:properties) do
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe IssueTrackerData do
|
RSpec.describe Integrations::IssueTrackerData do
|
||||||
describe 'associations' do
|
describe 'associations' do
|
||||||
it { is_expected.to belong_to :integration }
|
it { is_expected.to belong_to :integration }
|
||||||
end
|
end
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe JiraTrackerData do
|
RSpec.describe Integrations::JiraTrackerData do
|
||||||
describe 'associations' do
|
describe 'associations' do
|
||||||
it { is_expected.to belong_to(:integration) }
|
it { is_expected.to belong_to(:integration) }
|
||||||
end
|
end
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe OpenProjectTrackerData do
|
RSpec.describe Integrations::OpenProjectTrackerData do
|
||||||
describe 'associations' do
|
describe 'associations' do
|
||||||
it { is_expected.to belong_to(:integration) }
|
it { is_expected.to belong_to(:integration) }
|
||||||
end
|
end
|
|
@ -295,75 +295,69 @@ RSpec.describe 'Query.project(fullPath).releases()' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'sorting behavior' do
|
describe 'sorting and pagination' do
|
||||||
let_it_be(:today) { Time.now }
|
let_it_be(:sort_project) { create(:project, :public) }
|
||||||
let_it_be(:yesterday) { today - 1.day }
|
|
||||||
let_it_be(:tomorrow) { today + 1.day }
|
|
||||||
|
|
||||||
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) }
|
def pagination_query(params)
|
||||||
let_it_be(:release_v2) { create(:release, project: project, tag: 'v2', released_at: today, created_at: yesterday) }
|
graphql_query_for(
|
||||||
let_it_be(:release_v3) { create(:release, project: project, tag: 'v3', released_at: tomorrow, created_at: today) }
|
:project,
|
||||||
|
{ full_path: sort_project.full_path },
|
||||||
let(:current_user) { developer }
|
query_graphql_field(:releases, params, "#{page_info} nodes { tagName }")
|
||||||
|
)
|
||||||
let(:params) { nil }
|
|
||||||
|
|
||||||
let(:sorted_tags) do
|
|
||||||
graphql_data.dig('project', 'releases', 'nodes').map { |release| release['tagName'] }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:query) do
|
def pagination_results_data(nodes)
|
||||||
graphql_query_for(:project, { fullPath: project.full_path },
|
nodes.map { |release| release['tagName'] }
|
||||||
%{
|
|
||||||
releases#{params ? "(#{params})" : ""} {
|
|
||||||
nodes {
|
|
||||||
tagName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
context 'when sorting by released_at' do
|
||||||
post_query
|
let_it_be(:release5) { create(:release, project: sort_project, tag: 'v5.5.0', released_at: 3.days.from_now) }
|
||||||
end
|
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
|
context 'when ascending' do
|
||||||
it 'returns the results with the default sort applied (sort: RELEASED_AT_DESC)' do
|
it_behaves_like 'sorted paginated query' do
|
||||||
expect(sorted_tags).to eq(%w(v3 v2 v1))
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with sort: RELEASED_AT_DESC' do
|
context 'when sorting by created_at' do
|
||||||
let(:params) { 'sort: RELEASED_AT_DESC' }
|
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
|
context 'when ascending' do
|
||||||
expect(sorted_tags).to eq(%w(v3 v2 v1))
|
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
|
||||||
end
|
|
||||||
|
|
||||||
context 'with sort: RELEASED_AT_ASC' do
|
context 'when descending' do
|
||||||
let(:params) { 'sort: RELEASED_AT_ASC' }
|
it_behaves_like 'sorted paginated query' do
|
||||||
|
let(:sort_param) { :CREATED_DESC }
|
||||||
it 'returns the releases ordered by released_at in ascending order' do
|
let(:first_param) { 2 }
|
||||||
expect(sorted_tags).to eq(%w(v1 v2 v3))
|
let(:expected_results) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] }
|
||||||
end
|
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))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue