Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
27f5b61b11
commit
7e1b27cedb
|
|
@ -57,12 +57,7 @@ update-qa-cache:
|
|||
- install_gitlab_gem
|
||||
script:
|
||||
- ./scripts/trigger-build omnibus
|
||||
|
||||
package-and-qa:
|
||||
extends:
|
||||
- .package-and-qa-base
|
||||
- .qa:rules:package-and-qa
|
||||
# This job often times out, so temporarily use private runners and a long timeout: https://gitlab.com/gitlab-org/gitlab/-/issues/238563
|
||||
# These jobs often time out, so temporarily use private runners and a long timeout: https://gitlab.com/gitlab-org/gitlab/-/issues/238563
|
||||
tags:
|
||||
- prm
|
||||
timeout: 4h
|
||||
|
|
@ -71,3 +66,34 @@ package-and-qa:
|
|||
artifacts: false
|
||||
- job: build-assets-image
|
||||
artifacts: false
|
||||
|
||||
.package-and-qa-ff-base:
|
||||
needs:
|
||||
- detect-tests
|
||||
variables:
|
||||
CHANGED_FILES: tmp/changed_files.txt
|
||||
script:
|
||||
- export GITLAB_QA_OPTIONS="--set-feature-flags $(scripts/changed-feature-flags --files $(cat $CHANGED_FILES | tr ' ' ',') --state $QA_FF_STATE)"
|
||||
- echo $GITLAB_QA_OPTIONS
|
||||
- ./scripts/trigger-build omnibus
|
||||
|
||||
package-and-qa:
|
||||
extends:
|
||||
- .package-and-qa-base
|
||||
- .qa:rules:package-and-qa
|
||||
|
||||
package-and-qa-ff-enabled:
|
||||
extends:
|
||||
- .package-and-qa-base
|
||||
- .package-and-qa-ff-base
|
||||
- .qa:rules:package-and-qa:feature-flags
|
||||
variables:
|
||||
QA_FF_STATE: "enable"
|
||||
|
||||
package-and-qa-ff-disabled:
|
||||
extends:
|
||||
- .package-and-qa-base
|
||||
- .package-and-qa-ff-base
|
||||
- .qa:rules:package-and-qa:feature-flags
|
||||
variables:
|
||||
QA_FF_STATE: "disable"
|
||||
|
|
|
|||
|
|
@ -381,6 +381,9 @@
|
|||
- "config/helpers/**/*.js"
|
||||
- "vendor/assets/javascripts/**/*"
|
||||
|
||||
.feature-flag-config-patterns: &feature-flag-config-patterns
|
||||
- "{,ee/}config/feature_flags/**/*.yml"
|
||||
|
||||
################
|
||||
# Shared rules #
|
||||
################
|
||||
|
|
@ -682,6 +685,9 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
changes: *feature-flag-config-patterns
|
||||
when: never
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
changes: *ci-qa-patterns
|
||||
allow_failure: true
|
||||
|
|
@ -695,6 +701,14 @@
|
|||
- <<: *if-dot-com-gitlab-org-schedule
|
||||
allow_failure: true
|
||||
|
||||
.qa:rules:package-and-qa:feature-flags:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
changes: *feature-flag-config-patterns
|
||||
allow_failure: true
|
||||
|
||||
###############
|
||||
# Rails rules #
|
||||
###############
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@ export default {
|
|||
if (isScopedLabel(candidateLabel)) {
|
||||
const scopedKeyWithDelimiter = `${scopedLabelKey(candidateLabel)}${SCOPED_LABEL_DELIMITER}`;
|
||||
const currentActiveScopedLabel = state.labels.find(
|
||||
({ title }) => title.startsWith(scopedKeyWithDelimiter) && title !== candidateLabel.title,
|
||||
({ set, title }) =>
|
||||
set && title.startsWith(scopedKeyWithDelimiter) && title !== candidateLabel.title,
|
||||
);
|
||||
|
||||
if (currentActiveScopedLabel) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ module Integrations
|
|||
:add_pusher,
|
||||
:alert_events,
|
||||
:api_key,
|
||||
:api_token,
|
||||
:api_url,
|
||||
:bamboo_url,
|
||||
:branches_to_be_notified,
|
||||
|
|
@ -74,7 +75,8 @@ module Integrations
|
|||
:url,
|
||||
:user_key,
|
||||
:username,
|
||||
:webhook
|
||||
:webhook,
|
||||
:zentao_product_xid
|
||||
].freeze
|
||||
|
||||
def integration_params
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Clusters
|
||||
class AgentAuthorizationsFinder
|
||||
def initialize(project)
|
||||
@project = project
|
||||
end
|
||||
|
||||
def execute
|
||||
# closest, most-specific authorization for a given agent wins
|
||||
(project_authorizations + implicit_authorizations + group_authorizations)
|
||||
.uniq(&:agent_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project
|
||||
|
||||
def implicit_authorizations
|
||||
project.cluster_agents.map do |agent|
|
||||
Clusters::Agents::ImplicitAuthorization.new(agent: agent)
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def project_authorizations
|
||||
ancestor_ids = project.group ? project.ancestors.select(:id) : project.namespace_id
|
||||
|
||||
Clusters::Agents::ProjectAuthorization
|
||||
.where(project_id: project.id)
|
||||
.joins(agent: :project)
|
||||
.preload(agent: :project)
|
||||
.where(cluster_agents: { projects: { namespace_id: ancestor_ids } })
|
||||
.to_a
|
||||
end
|
||||
|
||||
def group_authorizations
|
||||
return [] unless project.group
|
||||
|
||||
authorizations = Clusters::Agents::GroupAuthorization.arel_table
|
||||
|
||||
ordered_ancestors_cte = Gitlab::SQL::CTE.new(
|
||||
:ordered_ancestors,
|
||||
project.group.self_and_ancestors(hierarchy_order: :asc).reselect(:id)
|
||||
)
|
||||
|
||||
cte_join_sources = authorizations.join(ordered_ancestors_cte.table).on(
|
||||
authorizations[:group_id].eq(ordered_ancestors_cte.table[:id])
|
||||
).join_sources
|
||||
|
||||
Clusters::Agents::GroupAuthorization
|
||||
.with(ordered_ancestors_cte.to_arel)
|
||||
.joins(cte_join_sources)
|
||||
.joins(agent: :project)
|
||||
.where('projects.namespace_id IN (SELECT id FROM ordered_ancestors)')
|
||||
.order(Arel.sql('agent_id, array_position(ARRAY(SELECT id FROM ordered_ancestors)::bigint[], agent_group_authorizations.group_id)'))
|
||||
.select('DISTINCT ON (agent_id) agent_group_authorizations.*')
|
||||
.preload(agent: :project)
|
||||
.to_a
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Clusters
|
||||
class DeployableAgentsFinder
|
||||
def initialize(project)
|
||||
@project = project
|
||||
end
|
||||
|
||||
def execute
|
||||
project.cluster_agents.ordered_by_name
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project
|
||||
end
|
||||
end
|
||||
|
|
@ -132,6 +132,20 @@ module IntegrationsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def zentao_issue_breadcrumb_link(issue)
|
||||
link_to issue[:web_url], { target: '_blank', rel: 'noopener noreferrer', class: 'gl-display-flex gl-align-items-center gl-white-space-nowrap' } do
|
||||
icon = image_tag image_path('logos/zentao.svg'), width: 15, height: 15, class: 'gl-mr-2'
|
||||
[icon, html_escape(issue[:id])].join.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def zentao_issues_show_data
|
||||
{
|
||||
issues_show_path: project_integrations_zentao_issue_path(@project, params[:id], format: :json),
|
||||
issues_list_path: project_integrations_zentao_issues_path(@project)
|
||||
}
|
||||
end
|
||||
|
||||
extend self
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class Integration < ApplicationRecord
|
|||
asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord
|
||||
drone_ci emails_on_push ewm external_wiki flowdock hangouts_chat irker jira
|
||||
mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email
|
||||
pivotaltracker prometheus pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack
|
||||
pivotaltracker prometheus pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack zentao
|
||||
].freeze
|
||||
|
||||
PROJECT_SPECIFIC_INTEGRATION_NAMES = %w[
|
||||
|
|
|
|||
|
|
@ -9,16 +9,25 @@ module Integrations
|
|||
validates :api_token, presence: true, if: :activated?
|
||||
validates :zentao_product_xid, presence: true, if: :activated?
|
||||
|
||||
def self.feature_flag_enabled?(project)
|
||||
Feature.enabled?(:zentao_issues_integration, project)
|
||||
end
|
||||
|
||||
# License Level: EEP_FEATURES
|
||||
def self.issues_license_available?(project)
|
||||
project&.licensed_feature_available?(:zentao_issues_integration)
|
||||
end
|
||||
|
||||
def data_fields
|
||||
zentao_tracker_data || self.build_zentao_tracker_data
|
||||
end
|
||||
|
||||
def title
|
||||
self.class.name.demodulize
|
||||
'ZenTao'
|
||||
end
|
||||
|
||||
def description
|
||||
s_("ZentaoIntegration|Use Zentao as this project's issue tracker.")
|
||||
s_("ZentaoIntegration|Use ZenTao as this project's issue tracker.")
|
||||
end
|
||||
|
||||
def self.to_param
|
||||
|
|
@ -42,28 +51,28 @@ module Integrations
|
|||
{
|
||||
type: 'text',
|
||||
name: 'url',
|
||||
title: s_('ZentaoIntegration|Zentao Web URL'),
|
||||
title: s_('ZentaoIntegration|ZenTao Web URL'),
|
||||
placeholder: 'https://www.zentao.net',
|
||||
help: s_('ZentaoIntegration|Base URL of the Zentao instance.'),
|
||||
help: s_('ZentaoIntegration|Base URL of the ZenTao instance.'),
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'api_url',
|
||||
title: s_('ZentaoIntegration|Zentao API URL (optional)'),
|
||||
title: s_('ZentaoIntegration|ZenTao API URL (optional)'),
|
||||
help: s_('ZentaoIntegration|If different from Web URL.')
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'api_token',
|
||||
title: s_('ZentaoIntegration|Zentao API token'),
|
||||
title: s_('ZentaoIntegration|ZenTao API token'),
|
||||
non_empty_password_title: s_('ZentaoIntegration|Enter API token'),
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'zentao_product_xid',
|
||||
title: s_('ZentaoIntegration|Zentao Product ID'),
|
||||
title: s_('ZentaoIntegration|ZenTao Product ID'),
|
||||
required: true
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class Namespace < ApplicationRecord
|
|||
|
||||
ignore_column :delayed_project_removal, remove_with: '14.1', remove_after: '2021-05-22'
|
||||
|
||||
# Tells ActiveRecord not to store the full class name, in order to space some space
|
||||
# Tells ActiveRecord not to store the full class name, in order to save some space
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69794
|
||||
self.store_full_sti_class = false
|
||||
self.store_full_class_name = false
|
||||
|
|
|
|||
|
|
@ -1453,7 +1453,10 @@ class Project < ApplicationRecord
|
|||
end
|
||||
|
||||
def disabled_integrations
|
||||
[:zentao]
|
||||
disabled_integrations = []
|
||||
disabled_integrations << :zentao unless ::Integrations::Zentao.feature_flag_enabled?(self)
|
||||
|
||||
disabled_integrations
|
||||
end
|
||||
|
||||
def find_or_initialize_integration(name)
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340166
|
|||
milestone: '14.3'
|
||||
type: development
|
||||
group: group::configure
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: zentao_issues_integration
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69602
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338775
|
||||
milestone: '14.4'
|
||||
type: development
|
||||
group: group::integrations
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: counts.projects_zentao_active
|
||||
name: count_all_projects_zentao_active
|
||||
description: Count of projects with active Zentao integrations
|
||||
product_section: dev
|
||||
product_stage: ecosystem
|
||||
product_group: group::integrations
|
||||
product_category: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.4"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: Operational
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: counts.groups_zentao_active
|
||||
name: count_all_groups_zentao_active
|
||||
description: Count of groups with active Zentao integrations
|
||||
product_section: dev
|
||||
product_stage: ecosystem
|
||||
product_group: group::integrations
|
||||
product_category: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.4"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: Operational
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: counts.instances_zentao_active
|
||||
name: count_all_instances_zentao_active
|
||||
description: Count of instances with active Zentao integrations
|
||||
product_section: dev
|
||||
product_stage: ecosystem
|
||||
product_group: group::integrations
|
||||
product_category: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.4"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: Operational
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: counts.projects_inheriting_zentao_active
|
||||
name: count_all_projects_inheriting_zentao_active
|
||||
description: Count of projects that inherit active Zentao integrations
|
||||
product_section: dev
|
||||
product_stage: ecosystem
|
||||
product_group: group::integrations
|
||||
product_category: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.4"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: Operational
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: counts.groups_inheriting_zentao_active
|
||||
name: count_all_groups_inheriting_zentao_active
|
||||
description: Count of groups that inherit active Zentao integrations
|
||||
product_section: dev
|
||||
product_stage: ecosystem
|
||||
product_group: group::integrations
|
||||
product_category: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.4"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: Operational
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -851,6 +851,10 @@ of choice already. Some examples include [HAProxy](https://www.haproxy.org/)
|
|||
Big-IP LTM, and Citrix Net Scaler. This documentation outlines what ports
|
||||
and protocols you need configure.
|
||||
|
||||
WARNING:
|
||||
Long-running background jobs can maintain an idle connection with Praefect for up 6 hours. Set your load balancer timeout to be at least
|
||||
6 hours long.
|
||||
|
||||
| LB Port | Backend Port | Protocol |
|
||||
|:--------|:-------------|:---------|
|
||||
| 2305 | 2305 | TCP |
|
||||
|
|
|
|||
|
|
@ -101,8 +101,12 @@ To look up a project's hash path using a Rails console:
|
|||
|
||||
#### From hashed path to project name
|
||||
|
||||
Administrators can look up a project's name from its hashed storage path using a Rails console. To
|
||||
look up a project's name from its hashed storage path:
|
||||
Administrators can look up a project's name from its hashed storage path using:
|
||||
|
||||
- A Rails console.
|
||||
- The `config` file in the `*.git` directory.
|
||||
|
||||
To look up a project's name using the Rails console:
|
||||
|
||||
1. Start a [Rails console](operations/rails_console.md#starting-a-rails-console-session).
|
||||
1. Run a command similar to this example:
|
||||
|
|
@ -121,6 +125,14 @@ The output includes the project ID and the project name. For example:
|
|||
=> #<Project id:16 it/supportteam/ticketsystem>
|
||||
```
|
||||
|
||||
To look up a project's name using the `config` file in the `*.git` directory:
|
||||
|
||||
1. Navigate to the to the `*.git` directory. This directory is located in `/var/opt/gitlab/git-data/repositories/@hashed/`, where the first four
|
||||
characters of the hash are the first two directories in the path under `@hashed/`. For example, on a default Omnibus GitLab installation the
|
||||
`*.git` directory of the hash `b17eb17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9` would be
|
||||
`/var/opt/gitlab/git-data/repositories/@hashed/b1/7e/b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9.git`.
|
||||
1. Open the `config` file and locate the `fullpath=` key under `[gitlab]`.
|
||||
|
||||
### Hashed object pools
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitaly/-/issues/1606) in GitLab 12.1.
|
||||
|
|
|
|||
|
|
@ -16578,6 +16578,7 @@ State of a Sentry error.
|
|||
| <a id="servicetypeunify_circuit_service"></a>`UNIFY_CIRCUIT_SERVICE` | UnifyCircuitService type. |
|
||||
| <a id="servicetypewebex_teams_service"></a>`WEBEX_TEAMS_SERVICE` | WebexTeamsService type. |
|
||||
| <a id="servicetypeyoutrack_service"></a>`YOUTRACK_SERVICE` | YoutrackService type. |
|
||||
| <a id="servicetypezentao_service"></a>`ZENTAO_SERVICE` | ZentaoService type. |
|
||||
|
||||
### `SharedRunnersSetting`
|
||||
|
||||
|
|
|
|||
|
|
@ -79,11 +79,21 @@ for details.
|
|||
|
||||
End-to-end tests should pass with a feature flag enabled before it is enabled on Staging or on GitLab.com. Tests that need to be updated should be identified as part of [quad-planning](https://about.gitlab.com/handbook/engineering/quality/quad-planning/). The relevant [counterpart Software Engineer in Test](https://about.gitlab.com/handbook/engineering/quality/#individual-contributors) is responsible for updating the tests or assisting another engineer to do so. However, if a change does not go through quad-planning and a required test update is not made, test failures could block deployment.
|
||||
|
||||
If a test enables a feature flag as describe above, it is sufficient to run the `package-and-qa` job in a merge request containing the relevant changes.
|
||||
Or, if the feature flag and relevant changes have already been merged, you can confirm that the tests
|
||||
pass on `main`. The end-to-end tests run on `main` every two hours, and the results are posted to a [Test
|
||||
Session Report, which is available in the testcase-sessions project](https://gitlab.com/gitlab-org/quality/testcase-sessions/-/issues?label_name%5B%5D=found%3Amaster).
|
||||
### Automatic test execution when a feature flag definition changes
|
||||
|
||||
If the relevant tests do not enable the feature flag themselves, you can check if the tests will need
|
||||
to be updated by opening a draft merge request that enables the flag by default and then running the `package-and-qa` job.
|
||||
If a merge request adds or edits a [feature flag definition file](../../feature_flags/index.md#feature-flag-definition-and-validation),
|
||||
two `package-and-qa` jobs will be included automatically in the merge request pipeline. One job will enable the defined
|
||||
feature flag and the other will disable it. The jobs execute the same suite of tests to confirm that they pass with if
|
||||
the feature flag is either enabled or disabled.
|
||||
|
||||
### Test execution during feature development
|
||||
|
||||
If an end-to-end test enables a feature flag, the end-to-end test suite can be used to test changes in a merge request
|
||||
by running the `package-and-qa` job in the merge request pipeline. If the feature flag and relevant changes have already been merged, you can confirm that the tests
|
||||
pass on the default branch. The end-to-end tests run on the default branch every two hours, and the results are posted to a [Test
|
||||
Session Report, which is available in the testcase-sessions project](https://gitlab.com/gitlab-org/quality/testcase-sessions/-/issues?label_name%5B%5D=found%3Amain).
|
||||
|
||||
If the relevant tests do not enable the feature flag themselves, you can check if the tests will need to be updated by opening
|
||||
a draft merge request that enables the flag by default via a [feature flag definition file](../../feature_flags/index.md#feature-flag-definition-and-validation).
|
||||
That will [automatically execute the end-to-end test suite](#automatic-test-execution-when-a-feature-flag-definition-changes).
|
||||
The merge request can be closed once the tests pass. If you need assistance to update the tests, please contact the relevant [stable counterpart in the Quality department](https://about.gitlab.com/handbook/engineering/quality/#individual-contributors), or any Software Engineer in Test if there is no stable counterpart for your group.
|
||||
|
|
|
|||
|
|
@ -61,14 +61,35 @@ Since the migration files are not autoloaded by Rails, you must manually
|
|||
load the migration file. To do so, you can use the `require_migration!` helper method
|
||||
which can automatically load the correct migration file based on the spec filename.
|
||||
|
||||
For example, if your spec file is named as `populate_foo_column_spec.rb` then the
|
||||
helper method tries to load `${schema_version}_populate_foo_column.rb` migration file.
|
||||
|
||||
In case there is no pattern between your spec file and the actual migration file,
|
||||
you can provide the migration filename without the schema version, like so:
|
||||
In GitLab 14.4 and later, you can use `require_migration!` to load migration files from spec files
|
||||
that contain the schema version in the filename (for example,
|
||||
`2021101412150000_populate_foo_column_spec.rb`).
|
||||
|
||||
```ruby
|
||||
require_migration!('populate_foo_column')
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe PopulateFooColumn do
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
In some cases, you must require multiple migration files to use them in your specs. Here, there's no
|
||||
pattern between your spec file and the other migration file. You can provide the migration filename
|
||||
like so:
|
||||
|
||||
```ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
require_migration!('populate_bar_column')
|
||||
|
||||
RSpec.describe PopulateFooColumn do
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
#### `table`
|
||||
|
|
|
|||
|
|
@ -44,5 +44,3 @@ Clicking an alert's row opens the alert drawer, which shows more information abo
|
|||
can also create an incident from the alert and update the alert status in the alert drawer.
|
||||
|
||||
Clicking an alert's name takes the user to the [alert details page](../../../operations/incident_management/alerts.md#alert-details-page).
|
||||
|
||||
For information on work in progress for the alerts dashboard, see [this epic](https://gitlab.com/groups/gitlab-org/-/epics/5041).
|
||||
|
|
|
|||
|
|
@ -153,9 +153,9 @@ gitops:
|
|||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5784) in GitLab 14.3.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available,
|
||||
ask an administrator to [enable the `group_authorized_agents` flag](../../../administration/feature_flags.md).
|
||||
On GitLab.com, this feature is available.
|
||||
On self-managed GitLab, by default this feature is available. To hide the
|
||||
feature, ask an administrator to [disable the feature flag](../../../administration/feature_flags.md) named `group_authorized_agents`. On
|
||||
GitLab.com, this feature is available.
|
||||
|
||||
If you use the same cluster across multiple projects, you can set up the CI/CD Tunnel
|
||||
to grant the Agent access to one or more groups. This way, all the projects that belong
|
||||
|
|
|
|||
|
|
@ -177,6 +177,36 @@ module API
|
|||
|
||||
present current_authenticated_job, with: Entities::Ci::Job
|
||||
end
|
||||
|
||||
desc 'Get current agents' do
|
||||
detail 'Retrieves a list of agents for the given job token'
|
||||
end
|
||||
route_setting :authentication, job_token_allowed: true
|
||||
get '/allowed_agents', feature_category: :kubernetes_management do
|
||||
validate_current_authenticated_job
|
||||
|
||||
status 200
|
||||
|
||||
pipeline = current_authenticated_job.pipeline
|
||||
project = current_authenticated_job.project
|
||||
|
||||
allowed_agents =
|
||||
if Feature.enabled?(:group_authorized_agents, project, default_enabled: :yaml)
|
||||
agent_authorizations = Clusters::AgentAuthorizationsFinder.new(project).execute
|
||||
Entities::Clusters::AgentAuthorization.represent(agent_authorizations)
|
||||
else
|
||||
associated_agents = Clusters::DeployableAgentsFinder.new(project).execute
|
||||
Entities::Clusters::Agent.represent(associated_agents)
|
||||
end
|
||||
|
||||
{
|
||||
allowed_agents: allowed_agents,
|
||||
job: Entities::Ci::JobRequest::JobInfo.represent(current_authenticated_job),
|
||||
pipeline: Entities::Ci::PipelineBasic.represent(pipeline),
|
||||
project: Entities::ProjectIdentity.represent(project),
|
||||
user: Entities::UserBasic.represent(current_user)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
helpers do
|
||||
|
|
@ -202,5 +232,3 @@ module API
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
API::Ci::Jobs.prepend_mod_with('API::Ci::Jobs')
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ module API
|
|||
type: Boolean,
|
||||
desc: 'DEPRECATED: This parameter has no effect since SSL verification will always be enabled'
|
||||
}
|
||||
],
|
||||
],
|
||||
'campfire' => [
|
||||
{
|
||||
required: true,
|
||||
|
|
@ -768,7 +768,33 @@ module API
|
|||
desc: 'The Webex Teams webhook. For example, https://api.ciscospark.com/v1/webhooks/incoming/...'
|
||||
},
|
||||
chat_notification_events
|
||||
].flatten
|
||||
].flatten,
|
||||
'zentao' => [
|
||||
{
|
||||
required: true,
|
||||
name: :url,
|
||||
type: String,
|
||||
desc: 'The base URL to the ZenTao instance web interface which is being linked to this GitLab project. For example, https://www.zentao.net'
|
||||
},
|
||||
{
|
||||
required: false,
|
||||
name: :api_url,
|
||||
type: String,
|
||||
desc: 'The base URL to the ZenTao instance API. Web URL value will be used if not set. For example, https://www.zentao.net'
|
||||
},
|
||||
{
|
||||
required: true,
|
||||
name: :api_token,
|
||||
type: String,
|
||||
desc: 'The API token created from ZenTao dashboard'
|
||||
},
|
||||
{
|
||||
required: true,
|
||||
name: :zentao_product_xid,
|
||||
type: String,
|
||||
desc: 'The product ID of ZenTao project'
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -805,7 +831,8 @@ module API
|
|||
::Integrations::Slack,
|
||||
::Integrations::SlackSlashCommands,
|
||||
::Integrations::Teamcity,
|
||||
::Integrations::Youtrack
|
||||
::Integrations::Youtrack,
|
||||
::Integrations::Zentao
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ class Gitlab::Ci::Build::AutoRetry
|
|||
RETRY_OVERRIDES = {
|
||||
ci_quota_exceeded: 0,
|
||||
no_matching_runner: 0,
|
||||
missing_dependency_failure: 0
|
||||
missing_dependency_failure: 0,
|
||||
forward_deployment_failure: 0
|
||||
}.freeze
|
||||
|
||||
def initialize(build)
|
||||
|
|
|
|||
|
|
@ -15,10 +15,8 @@ module Gitlab
|
|||
end
|
||||
|
||||
def ping
|
||||
response = fetch_product(zentao_product_xid)
|
||||
|
||||
response = fetch_product(zentao_product_xid) rescue {}
|
||||
active = response.fetch('deleted') == '0' rescue false
|
||||
|
||||
if active
|
||||
{ success: true }
|
||||
else
|
||||
|
|
@ -31,25 +29,30 @@ module Gitlab
|
|||
end
|
||||
|
||||
def fetch_issues(params = {})
|
||||
get("products/#{zentao_product_xid}/issues",
|
||||
params.reverse_merge(page: 1, limit: 20))
|
||||
get("products/#{zentao_product_xid}/issues", params)
|
||||
end
|
||||
|
||||
def fetch_issue(issue_id)
|
||||
raise Gitlab::Zentao::Client::Error unless issue_id_pattern.match(issue_id)
|
||||
|
||||
get("issues/#{issue_id}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def issue_id_pattern
|
||||
/\A\S+-\d+\z/
|
||||
end
|
||||
|
||||
def get(path, params = {})
|
||||
options = { headers: headers, query: params }
|
||||
response = Gitlab::HTTP.get(url(path), options)
|
||||
|
||||
return {} unless response.success?
|
||||
raise Gitlab::Zentao::Client::Error unless response.success?
|
||||
|
||||
Gitlab::Json.parse(response.body)
|
||||
rescue JSON::ParserError
|
||||
{}
|
||||
raise Gitlab::Zentao::Client::Error
|
||||
end
|
||||
|
||||
def url(path)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Zentao
|
||||
class Query
|
||||
STATUSES = %w[all opened closed].freeze
|
||||
ISSUES_DEFAULT_LIMIT = 20
|
||||
ISSUES_MAX_LIMIT = 50
|
||||
|
||||
attr_reader :client, :params
|
||||
|
||||
def initialize(integration, params)
|
||||
@client = Client.new(integration)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def issues
|
||||
issues_response = client.fetch_issues(query_options)
|
||||
return [] if issues_response.blank?
|
||||
|
||||
Kaminari.paginate_array(
|
||||
issues_response['issues'],
|
||||
limit: issues_response['limit'],
|
||||
total_count: issues_response['total']
|
||||
)
|
||||
end
|
||||
|
||||
def issue
|
||||
issue_response = client.fetch_issue(params[:id])
|
||||
issue_response['issue']
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def query_options
|
||||
{
|
||||
order: query_order,
|
||||
status: query_status,
|
||||
labels: query_labels,
|
||||
page: query_page,
|
||||
limit: query_limit,
|
||||
search: query_search
|
||||
}
|
||||
end
|
||||
|
||||
def query_page
|
||||
params[:page].presence || 1
|
||||
end
|
||||
|
||||
def query_limit
|
||||
limit = params[:limit].presence || ISSUES_DEFAULT_LIMIT
|
||||
[limit.to_i, ISSUES_MAX_LIMIT].min
|
||||
end
|
||||
|
||||
def query_search
|
||||
params[:search] || ''
|
||||
end
|
||||
|
||||
def query_order
|
||||
key, order = params['sort'].to_s.split('_', 2)
|
||||
zentao_key = (key == 'created' ? 'openedDate' : 'lastEditedDate')
|
||||
zentao_order = (order == 'asc' ? 'asc' : 'desc')
|
||||
|
||||
"#{zentao_key}_#{zentao_order}"
|
||||
end
|
||||
|
||||
def query_status
|
||||
return params[:state] if params[:state].present? && params[:state].in?(STATUSES)
|
||||
|
||||
'opened'
|
||||
end
|
||||
|
||||
def query_labels
|
||||
(params[:labels].presence || []).join(',')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module Projects
|
||||
module Menus
|
||||
class ZentaoMenu < ::Sidebars::Menu
|
||||
override :configure_menu_items
|
||||
def configure_menu_items
|
||||
render?.tap do |render|
|
||||
break unless render
|
||||
|
||||
add_items
|
||||
end
|
||||
end
|
||||
|
||||
override :link
|
||||
def link
|
||||
zentao_integration.url
|
||||
end
|
||||
|
||||
override :title
|
||||
def title
|
||||
s_('ZentaoIntegration|ZenTao issues')
|
||||
end
|
||||
|
||||
override :title_html_options
|
||||
def title_html_options
|
||||
{
|
||||
id: 'js-onboarding-settings-link'
|
||||
}
|
||||
end
|
||||
|
||||
override :image_path
|
||||
def image_path
|
||||
'logos/zentao.svg'
|
||||
end
|
||||
|
||||
# Hardcode sizes so image doesn't flash before CSS loads https://gitlab.com/gitlab-org/gitlab/-/issues/321022
|
||||
override :image_html_options
|
||||
def image_html_options
|
||||
{
|
||||
size: 16
|
||||
}
|
||||
end
|
||||
|
||||
override :render?
|
||||
def render?
|
||||
return false if zentao_integration.blank?
|
||||
|
||||
zentao_integration.active?
|
||||
end
|
||||
|
||||
def add_items
|
||||
add_item(open_zentao_menu_item)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def zentao_integration
|
||||
@zentao_integration ||= context.project.zentao_integration
|
||||
end
|
||||
|
||||
def open_zentao_menu_item
|
||||
::Sidebars::MenuItem.new(
|
||||
title: s_('ZentaoIntegration|Open ZenTao'),
|
||||
link: zentao_integration.url,
|
||||
active_routes: {},
|
||||
item_id: :open_zentao,
|
||||
sprite_icon: 'external-link',
|
||||
container_html_options: {
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
::Sidebars::Projects::Menus::ZentaoMenu.prepend_mod
|
||||
|
|
@ -23,6 +23,7 @@ module Sidebars
|
|||
add_menu(Sidebars::Projects::Menus::RepositoryMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::IssuesMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::ExternalIssueTrackerMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::ZentaoMenu.new(context)) if ::Integrations::Zentao.feature_flag_enabled?(context.project)
|
||||
add_menu(Sidebars::Projects::Menus::MergeRequestsMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::CiCdMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::SecurityComplianceMenu.new(context))
|
||||
|
|
|
|||
|
|
@ -39678,7 +39678,13 @@ msgstr ""
|
|||
msgid "ZenTaoIntegration|ZenTao user"
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|Base URL of the Zentao instance."
|
||||
msgid "Zentao issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|An error occurred while requesting data from the ZenTao service."
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|Base URL of the ZenTao instance."
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|Enter API token"
|
||||
|
|
@ -39687,19 +39693,31 @@ msgstr ""
|
|||
msgid "ZentaoIntegration|If different from Web URL."
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|Use Zentao as this project's issue tracker."
|
||||
msgid "ZentaoIntegration|Issue list"
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|Zentao API URL (optional)"
|
||||
msgid "ZentaoIntegration|Open ZenTao"
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|Zentao API token"
|
||||
msgid "ZentaoIntegration|Use ZenTao as this project's issue tracker."
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|Zentao Product ID"
|
||||
msgid "ZentaoIntegration|ZenTao API URL (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|Zentao Web URL"
|
||||
msgid "ZentaoIntegration|ZenTao API token"
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|ZenTao Product ID"
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|ZenTao Web URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|ZenTao issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "ZentaoIntegration|Zentao issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "Zoom meeting added"
|
||||
|
|
|
|||
|
|
@ -77,17 +77,24 @@ module QA
|
|||
def log_fabrication(method, resource, parents, args)
|
||||
start = Time.now
|
||||
|
||||
yield.tap do
|
||||
Support::FabricationTracker.start_fabrication
|
||||
result = yield.tap do
|
||||
fabrication_time = Time.now - start
|
||||
|
||||
Support::FabricationTracker.save_fabrication(:"#{method}_fabrication", fabrication_time * 1000)
|
||||
Runtime::Logger.debug do
|
||||
msg = ["==#{'=' * parents.size}>"]
|
||||
msg << "Built a #{name}"
|
||||
msg << "as a dependency of #{parents.last}" if parents.any?
|
||||
msg << "via #{method}"
|
||||
msg << "in #{Time.now - start} seconds"
|
||||
msg << "in #{fabrication_time} seconds"
|
||||
|
||||
msg.join(' ')
|
||||
end
|
||||
end
|
||||
Support::FabricationTracker.finish_fabrication
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# Define custom attribute
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ module QA
|
|||
Project.fabricate! do |resource|
|
||||
resource.name = 'project-for-issues'
|
||||
resource.description = 'project for adding issues'
|
||||
resource.api_client = api_client
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -93,6 +94,52 @@ module QA
|
|||
attempts: attempts
|
||||
)
|
||||
end
|
||||
|
||||
# Object comparison
|
||||
#
|
||||
# @param [QA::Resource::Issue] other
|
||||
# @return [Boolean]
|
||||
def ==(other)
|
||||
other.is_a?(Issue) && comparable_issue == other.comparable_issue
|
||||
end
|
||||
|
||||
# Override inspect for a better rspec failure diff output
|
||||
#
|
||||
# @return [String]
|
||||
def inspect
|
||||
JSON.pretty_generate(comparable_issue)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Return subset of fields for comparing issues
|
||||
#
|
||||
# @return [Hash]
|
||||
def comparable_issue
|
||||
reload! if api_response.nil?
|
||||
|
||||
api_resource.slice(
|
||||
:state,
|
||||
:description,
|
||||
:type,
|
||||
:title,
|
||||
:labels,
|
||||
:milestone,
|
||||
:upvotes,
|
||||
:downvotes,
|
||||
:merge_requests_count,
|
||||
:user_notes_count,
|
||||
:due_date,
|
||||
:has_tasks,
|
||||
:task_status,
|
||||
:confidential,
|
||||
:discussion_locked,
|
||||
:issue_type,
|
||||
:task_completion_status,
|
||||
:closed_at,
|
||||
:created_at
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,15 +5,16 @@ require 'active_support/core_ext/object/blank'
|
|||
module QA
|
||||
module Runtime
|
||||
class Feature
|
||||
SetFeatureError = Class.new(RuntimeError)
|
||||
AuthorizationError = Class.new(RuntimeError)
|
||||
UnknownScopeError = Class.new(RuntimeError)
|
||||
UnknownStateError = Class.new(RuntimeError)
|
||||
|
||||
class << self
|
||||
# Documentation: https://docs.gitlab.com/ee/api/features.html
|
||||
|
||||
include Support::API
|
||||
|
||||
SetFeatureError = Class.new(RuntimeError)
|
||||
AuthorizationError = Class.new(RuntimeError)
|
||||
UnknownScopeError = Class.new(RuntimeError)
|
||||
|
||||
def remove(key)
|
||||
request = Runtime::API::Request.new(api_client, "/features/#{key}")
|
||||
response = delete(request.url)
|
||||
|
|
@ -30,6 +31,23 @@ module QA
|
|||
set_and_verify(key, enable: false, **scopes)
|
||||
end
|
||||
|
||||
# Set one or more flags to their specified state.
|
||||
#
|
||||
# @param [Hash] flags The feature flags and desired values, e.g., { 'flag1' => 'enabled', 'flag2' => "disabled" }
|
||||
# @param [Hash] scopes The scope (user, project, group) to apply the feature flag to.
|
||||
def set(flags, **scopes)
|
||||
flags.each_pair do |flag, state|
|
||||
case state
|
||||
when 'enabled', 'enable', 'true', 1, true
|
||||
enable(flag, **scopes)
|
||||
when 'disabled', 'disable', 'false', 0, false
|
||||
disable(flag, **scopes)
|
||||
else
|
||||
raise UnknownStateError, "Unknown feature flag state: #{state}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def enabled?(key, **scopes)
|
||||
feature = JSON.parse(get_features).find { |flag| flag['name'] == key.to_s }
|
||||
feature && (feature['state'] == 'on' || feature['state'] == 'conditional' && scopes.present? && enabled_scope?(feature['gates'], **scopes))
|
||||
|
|
@ -47,15 +65,15 @@ module QA
|
|||
scopes.each do |key, value|
|
||||
case key
|
||||
when :project, :group, :user
|
||||
actors = gates.filter { |i| i['key'] == 'actors' }.first['value']
|
||||
break actors.include?("#{key.to_s.capitalize}:#{value.id}")
|
||||
actors = gates.find { |i| i['key'] == 'actors' }['value']
|
||||
return actors.include?("#{key.to_s.capitalize}:#{value.id}")
|
||||
when :feature_group
|
||||
groups = gates.filter { |i| i['key'] == 'groups' }.first['value']
|
||||
break groups.include?(value)
|
||||
else
|
||||
raise UnknownScopeError, "Unknown scope: #{key}"
|
||||
groups = gates.find { |i| i['key'] == 'groups' }['value']
|
||||
return groups.include?(value)
|
||||
end
|
||||
end
|
||||
|
||||
raise UnknownScopeError, "Unknown scope in: #{scopes}"
|
||||
end
|
||||
|
||||
def get_features
|
||||
|
|
|
|||
|
|
@ -17,6 +17,22 @@ module QA
|
|||
|
||||
arguments = OptionParser.new do |parser|
|
||||
options.to_a.each do |opt|
|
||||
# The argument for the --set-feature-flags option should look something like "flag1=enabled,flag2=disabled"
|
||||
# Here we translate that string into a hash, e.g.: { 'flag1' => 'enabled', 'flag2' => "disabled" }
|
||||
if opt.name == :set_feature_flags
|
||||
parser.on(opt.arg, opt.desc) do |flags|
|
||||
value = flags.split(',').each_with_object({}) do |pair, hash|
|
||||
flag_name, flag_value = pair.split('=')
|
||||
raise '--set-feature-flags requires flag name and flag state for each flag, e.g., flag1=enabled,flag2=disabled' unless flag_name && flag_value
|
||||
|
||||
hash[flag_name] = flag_value
|
||||
end
|
||||
Runtime::Scenario.define(opt.name, value)
|
||||
end
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
parser.on(opt.arg, opt.desc) do |value|
|
||||
Runtime::Scenario.define(opt.name, value)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ module QA
|
|||
attribute :gitlab_address, '--address URL', 'Address of the instance to test'
|
||||
attribute :enable_feature, '--enable-feature FEATURE_FLAG', 'Enable a feature before running tests'
|
||||
attribute :disable_feature, '--disable-feature FEATURE_FLAG', 'Disable a feature before running tests'
|
||||
attribute :set_feature_flags, '--set-feature-flags FEATURE_FLAGS',
|
||||
'Set one or more feature flags before running tests. ' \
|
||||
'Specify FEATURE_FLAGS as comma-separated flag=state pairs, e.g., "flag1=enabled,flag2=disabled"'
|
||||
attribute :parallel, '--parallel', 'Execute tests in parallel'
|
||||
attribute :loop, '--loop', 'Execute test repeatedly'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ module QA
|
|||
Runtime::Release.perform_before_hooks
|
||||
|
||||
Runtime::Feature.enable(options[:enable_feature]) if options.key?(:enable_feature)
|
||||
|
||||
Runtime::Feature.disable(options[:disable_feature]) if options.key?(:disable_feature) && (@feature_enabled = Runtime::Feature.enabled?(options[:disable_feature]))
|
||||
Runtime::Feature.set(options[:set_feature_flags]) if options.key?(:set_feature_flags)
|
||||
|
||||
Specs::Runner.perform do |specs|
|
||||
specs.tty = true
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ module QA
|
|||
sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
|
||||
end
|
||||
|
||||
after do
|
||||
user.remove_via_api!
|
||||
ensure
|
||||
Runtime::Feature.disable(:top_level_group_creation_enabled) if staging?
|
||||
end
|
||||
|
||||
context 'with subgroups and labels' do
|
||||
let(:subgroup) do
|
||||
Resource::Group.fabricate_via_api! do |group|
|
||||
|
|
@ -155,12 +161,6 @@ module QA
|
|||
expect(imported_member.access_level).to eq(Resource::Members::AccessLevel::DEVELOPER)
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
user.remove_via_api!
|
||||
ensure
|
||||
Runtime::Feature.disable(:top_level_group_creation_enabled) if staging?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
let(:imported_projects) do
|
||||
imported_group.reload!.projects
|
||||
end
|
||||
|
||||
before do
|
||||
Runtime::Feature.enable(:bulk_import_projects)
|
||||
Runtime::Feature.enable(:top_level_group_creation_enabled) if staging?
|
||||
|
|
@ -67,13 +71,48 @@ module QA
|
|||
) do
|
||||
expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration)
|
||||
|
||||
imported_projects = imported_group.reload!.projects
|
||||
aggregate_failures do
|
||||
expect(imported_projects.count).to eq(1)
|
||||
expect(imported_projects.first).to eq(source_project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with project issues' do
|
||||
let(:source_issue) do
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.api_client = api_client
|
||||
issue.project = source_project
|
||||
issue.labels = %w[label_one label_two]
|
||||
end
|
||||
end
|
||||
|
||||
let(:imported_issues) do
|
||||
imported_projects.first.issues
|
||||
end
|
||||
|
||||
let(:imported_issue) do
|
||||
issue = imported_issues.first
|
||||
Resource::Issue.init do |resource|
|
||||
resource.api_client = api_client
|
||||
resource.project = imported_projects.first
|
||||
resource.iid = issue[:iid]
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
source_issue # fabricate source group, project, issue
|
||||
end
|
||||
|
||||
it 'successfully imports issue' do
|
||||
expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration)
|
||||
|
||||
aggregate_failures do
|
||||
expect(imported_issues.count).to eq(1)
|
||||
expect(imported_issue.reload!).to eq(source_issue)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Support
|
||||
# Threadsafe fabrication time tracker
|
||||
#
|
||||
# Ongoing fabrication is added to callstack by start_fabrication and taken out by finish_fabrication
|
||||
#
|
||||
# Fabrication runtime is saved only for the first fabrication in the stack to properly represent the real time
|
||||
# fabrications might take as top level fabrication runtime will always include nested fabrications runtime
|
||||
#
|
||||
class FabricationTracker
|
||||
class << self
|
||||
# Start fabrication and increment ongoing fabrication count
|
||||
#
|
||||
# @return [void]
|
||||
def start_fabrication
|
||||
Thread.current[:fabrications_ongoing] = 0 unless Thread.current.key?(:fabrications_ongoing)
|
||||
|
||||
Thread.current[:fabrications_ongoing] += 1
|
||||
end
|
||||
|
||||
# Finish fabrication and decrement ongoing fabrication count
|
||||
#
|
||||
# @return [void]
|
||||
def finish_fabrication
|
||||
Thread.current[:fabrications_ongoing] -= 1
|
||||
end
|
||||
|
||||
# Save fabrication time if it's first in fabrication stack
|
||||
#
|
||||
# @param [Symbol] type
|
||||
# @param [Symbol] time
|
||||
# @return [void]
|
||||
def save_fabrication(type, time)
|
||||
return unless Thread.current.key?(type)
|
||||
return unless top_level_fabrication?
|
||||
|
||||
Thread.current[type] += time
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Check if current fabrication is the only one in the stack
|
||||
#
|
||||
# @return [Boolean]
|
||||
def top_level_fabrication?
|
||||
Thread.current[:fabrications_ongoing] == 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -57,6 +57,8 @@ module QA
|
|||
# @return [Hash]
|
||||
def test_stats(example)
|
||||
file_path = example.metadata[:file_path].gsub('./qa/specs/features', '')
|
||||
api_fabrication = ((example.metadata[:api_fabrication] || 0) * 1000).round
|
||||
ui_fabrication = ((example.metadata[:browser_ui_fabrication] || 0) * 1000).round
|
||||
|
||||
{
|
||||
name: 'test-stats',
|
||||
|
|
@ -76,6 +78,9 @@ module QA
|
|||
fields: {
|
||||
id: example.id,
|
||||
run_time: (example.execution_result.run_time * 1000).round,
|
||||
api_fabrication: api_fabrication,
|
||||
ui_fabrication: ui_fabrication,
|
||||
total_fabrication: api_fabrication + ui_fabrication,
|
||||
retry_attempts: example.metadata[:retry_attempts] || 0,
|
||||
job_url: QA::Runtime::Env.ci_job_url,
|
||||
pipeline_url: env('CI_PIPELINE_URL'),
|
||||
|
|
@ -98,14 +103,18 @@ module QA
|
|||
#
|
||||
# @return [String]
|
||||
def job_name
|
||||
@job_name ||= QA::Runtime::Env.ci_job_name.gsub(%r{ \d{1,2}/\d{1,2}}, '')
|
||||
@job_name ||= QA::Runtime::Env.ci_job_name&.gsub(%r{ \d{1,2}/\d{1,2}}, '')
|
||||
end
|
||||
|
||||
# Single common timestamp for all exported example metrics to keep data points consistently grouped
|
||||
#
|
||||
# @return [Time]
|
||||
def time
|
||||
@time ||= DateTime.strptime(env('CI_PIPELINE_CREATED_AT')).to_time
|
||||
@time ||= begin
|
||||
return Time.now unless env('CI_PIPELINE_CREATED_AT')
|
||||
|
||||
DateTime.strptime(env('CI_PIPELINE_CREATED_AT')).to_time
|
||||
end
|
||||
end
|
||||
|
||||
# Is a merge request execution
|
||||
|
|
|
|||
|
|
@ -175,6 +175,20 @@ RSpec.describe QA::Runtime::Feature do
|
|||
expect(described_class.enabled?(feature_flag)).to be_truthy
|
||||
end
|
||||
|
||||
it 'raises an error when the scope is unknown' do
|
||||
expect(QA::Runtime::API::Request)
|
||||
.to receive(:new)
|
||||
.with(api_client, "/features")
|
||||
.and_return(request)
|
||||
expect(described_class)
|
||||
.to receive(:get)
|
||||
.and_return(
|
||||
Struct.new(:code, :body)
|
||||
.new(200, %([{ "name": "a_flag", "state": "conditional", "gates": { "key": "groups", "value": ["foo"] } }])))
|
||||
|
||||
expect { described_class.enabled?(feature_flag, scope: 'foo') }.to raise_error(QA::Runtime::Feature::UnknownScopeError)
|
||||
end
|
||||
|
||||
context 'when a project scope is provided' do
|
||||
it_behaves_like 'checks a feature flag' do
|
||||
let(:scope) { :project }
|
||||
|
|
@ -212,4 +226,38 @@ RSpec.describe QA::Runtime::Feature do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.set' do
|
||||
let(:scope) { { scope: 'actor' } }
|
||||
|
||||
it 'raises an error when the flag state is unknown' do
|
||||
expect(described_class).not_to receive(:enable)
|
||||
expect(described_class).not_to receive(:disable)
|
||||
|
||||
expect { described_class.set({ foo: 'bar' }, **scope) }.to raise_error(QA::Runtime::Feature::UnknownStateError, 'Unknown feature flag state: bar')
|
||||
end
|
||||
|
||||
it 'enables feature flags' do
|
||||
expect(described_class).to receive(:enable).with(:flag1, scope)
|
||||
expect(described_class).to receive(:enable).with(:flag2, scope)
|
||||
expect(described_class).not_to receive(:disable)
|
||||
|
||||
described_class.set({ flag1: 'enabled', flag2: 'enable' }, **scope)
|
||||
end
|
||||
|
||||
it 'disables feature flags' do
|
||||
expect(described_class).to receive(:disable).with(:flag1, scope)
|
||||
expect(described_class).to receive(:disable).with(:flag2, scope)
|
||||
expect(described_class).not_to receive(:enable)
|
||||
|
||||
described_class.set({ flag1: 'disable', flag2: 'disable' }, **scope)
|
||||
end
|
||||
|
||||
it 'enables and disables feature flags' do
|
||||
expect(described_class).to receive(:enable).with(:flag1, scope)
|
||||
expect(described_class).to receive(:disable).with(:flag2, scope)
|
||||
|
||||
described_class.set({ flag1: 'enabled', flag2: 'disabled' }, **scope)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,8 +27,12 @@ RSpec.configure do |config|
|
|||
config.add_formatter QA::Support::Formatters::QuarantineFormatter
|
||||
config.add_formatter QA::Support::Formatters::TestStatsFormatter if QA::Runtime::Env.export_metrics?
|
||||
|
||||
config.before do |example|
|
||||
config.prepend_before do |example|
|
||||
QA::Runtime::Logger.debug("\nStarting test: #{example.full_description}\n")
|
||||
|
||||
# Reset fabrication counters tracked in resource base
|
||||
Thread.current[:api_fabrication] = 0
|
||||
Thread.current[:browser_ui_fabrication] = 0
|
||||
end
|
||||
|
||||
config.after do
|
||||
|
|
@ -36,6 +40,12 @@ RSpec.configure do |config|
|
|||
QA::Git::Repository.new.delete_netrc
|
||||
end
|
||||
|
||||
# Add fabrication time to spec metadata
|
||||
config.append_after do |example|
|
||||
example.metadata[:api_fabrication] = Thread.current[:api_fabrication]
|
||||
example.metadata[:browser_ui_fabrication] = Thread.current[:browser_ui_fabrication]
|
||||
end
|
||||
|
||||
config.after(:context) do
|
||||
if !QA::Runtime::Browser.blank_page? && QA::Page::Main::Menu.perform(&:signed_in?)
|
||||
QA::Page::Main::Menu.perform(&:sign_out)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ describe QA::Support::Formatters::TestStatsFormatter do
|
|||
let(:influx_write_api) { instance_double('InfluxDB2::WriteApi', write: nil) }
|
||||
let(:stage) { '1_manage' }
|
||||
let(:file_path) { "./qa/specs/features/#{stage}/subfolder/some_spec.rb" }
|
||||
let(:ui_fabrication) { 0 }
|
||||
let(:api_fabrication) { 0 }
|
||||
|
||||
let(:influx_client_args) do
|
||||
{
|
||||
|
|
@ -48,6 +50,9 @@ describe QA::Support::Formatters::TestStatsFormatter do
|
|||
fields: {
|
||||
id: './spec/support/formatters/test_stats_formatter_spec.rb[1:1]',
|
||||
run_time: 0,
|
||||
api_fabrication: api_fabrication * 1000,
|
||||
ui_fabrication: ui_fabrication * 1000,
|
||||
total_fabrication: (api_fabrication + ui_fabrication) * 1000,
|
||||
retry_attempts: 0,
|
||||
job_url: ci_job_url,
|
||||
pipeline_url: ci_pipeline_url,
|
||||
|
|
@ -69,6 +74,11 @@ describe QA::Support::Formatters::TestStatsFormatter do
|
|||
RSpec::Core::Sandbox.sandboxed do |config|
|
||||
config.formatter = QA::Support::Formatters::TestStatsFormatter
|
||||
|
||||
config.append_after do |example|
|
||||
example.metadata[:api_fabrication] = Thread.current[:api_fabrication]
|
||||
example.metadata[:browser_ui_fabrication] = Thread.current[:browser_ui_fabrication]
|
||||
end
|
||||
|
||||
config.before(:context) { RSpec.current_example = nil }
|
||||
|
||||
example.run
|
||||
|
|
@ -171,5 +181,21 @@ describe QA::Support::Formatters::TestStatsFormatter do
|
|||
expect(influx_write_api).to have_received(:write).with(data: [data])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with fabrication runtimes' do
|
||||
let(:ui_fabrication) { 10 }
|
||||
let(:api_fabrication) { 4 }
|
||||
|
||||
before do
|
||||
Thread.current[:api_fabrication] = api_fabrication
|
||||
Thread.current[:browser_ui_fabrication] = ui_fabrication
|
||||
end
|
||||
|
||||
it 'exports data to influxdb with fabrication times' do
|
||||
run_spec
|
||||
|
||||
expect(influx_write_api).to have_received(:write).with(data: [data])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'yaml'
|
||||
require 'optparse'
|
||||
require_relative 'api/default_options'
|
||||
|
||||
# This script returns the desired feature flag state as a comma-separated string for the feature flags in the specified files.
|
||||
# Each desired feature flag state is specified as 'feature-flag=state'.
|
||||
#
|
||||
# For example, if the specified files included `config/feature_flags/development/ci_yaml_limit_size.yml` and the desired
|
||||
# state as specified by the second argument was enabled, the value returned would be `ci_yaml_limit_size=enabled`
|
||||
|
||||
class GetFeatureFlagsFromFiles
|
||||
def initialize(options)
|
||||
@files = options.delete(:files)
|
||||
@state = options.delete(:state)
|
||||
end
|
||||
|
||||
def extracted_flags
|
||||
files.each_with_object([]) do |file_path, all|
|
||||
next unless file_path =~ %r{/feature_flags/development/.*\.yml}
|
||||
next unless File.exist?(file_path)
|
||||
|
||||
ff_yaml = YAML.safe_load(File.read(file_path))
|
||||
ff_to_add = "#{ff_yaml['name']}"
|
||||
ff_to_add += "=#{state}" unless state.to_s.empty?
|
||||
|
||||
all << ff_to_add
|
||||
end.join(',')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :files, :state
|
||||
end
|
||||
|
||||
if $0 == __FILE__
|
||||
options = API::DEFAULT_OPTIONS.dup
|
||||
|
||||
OptionParser.new do |opts|
|
||||
opts.on("-f", "--files FILES", Array, "Comma-separated list of feature flag config files") do |value|
|
||||
options[:files] = value
|
||||
end
|
||||
|
||||
opts.on("-s", "--state STATE", String,
|
||||
"The desired state of the feature flags (enabled or disabled). If not specified the output will only list the feature flags."
|
||||
) do |value|
|
||||
options[:state] = value
|
||||
end
|
||||
|
||||
opts.on("-h", "--help", "Prints this help") do
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
end.parse!
|
||||
|
||||
puts GetFeatureFlagsFromFiles.new(options).extracted_flags
|
||||
end
|
||||
|
|
@ -154,7 +154,8 @@ module Trigger
|
|||
'SECURITY_SOURCES' => Trigger.security? ? 'true' : 'false',
|
||||
'ee' => Trigger.ee? ? 'true' : 'false',
|
||||
'QA_BRANCH' => ENV['QA_BRANCH'] || 'master',
|
||||
'CACHE_UPDATE' => ENV['OMNIBUS_GITLAB_CACHE_UPDATE']
|
||||
'CACHE_UPDATE' => ENV['OMNIBUS_GITLAB_CACHE_UPDATE'],
|
||||
'GITLAB_QA_OPTIONS' => ENV['GITLAB_QA_OPTIONS']
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::AgentAuthorizationsFinder do
|
||||
describe '#execute' do
|
||||
let_it_be(:top_level_group) { create(:group) }
|
||||
let_it_be(:subgroup1) { create(:group, parent: top_level_group) }
|
||||
let_it_be(:subgroup2) { create(:group, parent: subgroup1) }
|
||||
let_it_be(:bottom_level_group) { create(:group, parent: subgroup2) }
|
||||
|
||||
let_it_be(:agent_configuration_project) { create(:project, namespace: subgroup1) }
|
||||
let_it_be(:requesting_project, reload: true) { create(:project, namespace: bottom_level_group) }
|
||||
|
||||
let_it_be(:staging_agent) { create(:cluster_agent, project: agent_configuration_project) }
|
||||
let_it_be(:production_agent) { create(:cluster_agent, project: agent_configuration_project) }
|
||||
|
||||
subject { described_class.new(requesting_project).execute }
|
||||
|
||||
describe 'project authorizations' do
|
||||
context 'agent configuration project does not share a root namespace with the given project' do
|
||||
let(:unrelated_agent) { create(:cluster_agent) }
|
||||
|
||||
before do
|
||||
create(:agent_project_authorization, agent: unrelated_agent, project: requesting_project)
|
||||
end
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'with project authorizations present' do
|
||||
let!(:authorization) {create(:agent_project_authorization, agent: production_agent, project: requesting_project) }
|
||||
|
||||
it { is_expected.to match_array [authorization] }
|
||||
end
|
||||
|
||||
context 'with overlapping authorizations' do
|
||||
let!(:agent) { create(:cluster_agent, project: requesting_project) }
|
||||
let!(:project_authorization) { create(:agent_project_authorization, agent: agent, project: requesting_project) }
|
||||
let!(:group_authorization) { create(:agent_group_authorization, agent: agent, group: bottom_level_group) }
|
||||
|
||||
it { is_expected.to match_array [project_authorization] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'implicit authorizations' do
|
||||
let!(:associated_agent) { create(:cluster_agent, project: requesting_project) }
|
||||
|
||||
it 'returns authorizations for agents directly associated with the project' do
|
||||
expect(subject.count).to eq(1)
|
||||
|
||||
authorization = subject.first
|
||||
expect(authorization).to be_a(Clusters::Agents::ImplicitAuthorization)
|
||||
expect(authorization.agent).to eq(associated_agent)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'authorized groups' do
|
||||
context 'agent configuration project is outside the requesting project hierarchy' do
|
||||
let(:unrelated_agent) { create(:cluster_agent) }
|
||||
|
||||
before do
|
||||
create(:agent_group_authorization, agent: unrelated_agent, group: top_level_group)
|
||||
end
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'multiple agents are authorized for the same group' do
|
||||
let!(:staging_auth) { create(:agent_group_authorization, agent: staging_agent, group: bottom_level_group) }
|
||||
let!(:production_auth) { create(:agent_group_authorization, agent: production_agent, group: bottom_level_group) }
|
||||
|
||||
it 'returns authorizations for all agents' do
|
||||
expect(subject).to contain_exactly(staging_auth, production_auth)
|
||||
end
|
||||
end
|
||||
|
||||
context 'a single agent is authorized to more than one matching group' do
|
||||
let!(:bottom_level_auth) { create(:agent_group_authorization, agent: production_agent, group: bottom_level_group) }
|
||||
let!(:top_level_auth) { create(:agent_group_authorization, agent: production_agent, group: top_level_group) }
|
||||
|
||||
it 'picks the authorization for the closest group to the requesting project' do
|
||||
expect(subject).to contain_exactly(bottom_level_auth)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Clusters::DeployableAgentsFinder do
|
||||
describe '#execute' do
|
||||
let_it_be(:agent) { create(:cluster_agent) }
|
||||
|
||||
let(:project) { agent.project }
|
||||
|
||||
subject { described_class.new(project).execute }
|
||||
|
||||
it { is_expected.to contain_exactly(agent) }
|
||||
end
|
||||
end
|
||||
|
|
@ -159,8 +159,9 @@ describe('LabelsSelect Mutations', () => {
|
|||
labels = [
|
||||
{ id: 1, title: 'scoped' },
|
||||
{ id: 2, title: 'scoped::one', set: false },
|
||||
{ id: 3, title: 'scoped::test', set: true },
|
||||
{ id: 4, title: '' },
|
||||
{ id: 3, title: 'scoped::two', set: false },
|
||||
{ id: 4, title: 'scoped::three', set: true },
|
||||
{ id: 5, title: '' },
|
||||
];
|
||||
});
|
||||
|
||||
|
|
@ -191,8 +192,9 @@ describe('LabelsSelect Mutations', () => {
|
|||
expect(state.labels).toEqual([
|
||||
{ id: 1, title: 'scoped' },
|
||||
{ id: 2, title: 'scoped::one', set: true, touched: true },
|
||||
{ id: 3, title: 'scoped::test', set: false },
|
||||
{ id: 4, title: '' },
|
||||
{ id: 3, title: 'scoped::two', set: false },
|
||||
{ id: 4, title: 'scoped::three', set: false },
|
||||
{ id: 5, title: '' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ RSpec.describe Gitlab::Ci::Build::AutoRetry do
|
|||
"quota is exceeded" | 0 | { max: 2 } | :ci_quota_exceeded | false
|
||||
"no matching runner" | 0 | { max: 2 } | :no_matching_runner | false
|
||||
"missing dependencies" | 0 | { max: 2 } | :missing_dependency_failure | false
|
||||
"forward deployment failure" | 0 | { max: 2 } | :forward_deployment_failure | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
|
|||
|
|
@ -6,7 +6,23 @@ RSpec.describe Gitlab::Zentao::Client do
|
|||
subject(:integration) { described_class.new(zentao_integration) }
|
||||
|
||||
let(:zentao_integration) { create(:zentao_integration) }
|
||||
let(:mock_get_products_url) { integration.send(:url, "products/#{zentao_integration.zentao_product_xid}") }
|
||||
|
||||
def mock_get_products_url
|
||||
integration.send(:url, "products/#{zentao_integration.zentao_product_xid}")
|
||||
end
|
||||
|
||||
def mock_fetch_issue_url(issue_id)
|
||||
integration.send(:url, "issues/#{issue_id}")
|
||||
end
|
||||
|
||||
let(:mock_headers) do
|
||||
{
|
||||
headers: {
|
||||
'Content-Type' => 'application/json',
|
||||
'Token' => zentao_integration.api_token
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
describe '#new' do
|
||||
context 'if integration is nil' do
|
||||
|
|
@ -25,15 +41,6 @@ RSpec.describe Gitlab::Zentao::Client do
|
|||
end
|
||||
|
||||
describe '#fetch_product' do
|
||||
let(:mock_headers) do
|
||||
{
|
||||
headers: {
|
||||
'Content-Type' => 'application/json',
|
||||
'Token' => zentao_integration.api_token
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'with valid product' do
|
||||
let(:mock_response) { { 'id' => zentao_integration.zentao_product_xid } }
|
||||
|
||||
|
|
@ -54,7 +61,9 @@ RSpec.describe Gitlab::Zentao::Client do
|
|||
end
|
||||
|
||||
it 'fetches the empty product' do
|
||||
expect(integration.fetch_product(zentao_integration.zentao_product_xid)).to eq({})
|
||||
expect do
|
||||
integration.fetch_product(zentao_integration.zentao_product_xid)
|
||||
end.to raise_error(Gitlab::Zentao::Client::Error)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -65,21 +74,14 @@ RSpec.describe Gitlab::Zentao::Client do
|
|||
end
|
||||
|
||||
it 'fetches the empty product' do
|
||||
expect(integration.fetch_product(zentao_integration.zentao_product_xid)).to eq({})
|
||||
expect do
|
||||
integration.fetch_product(zentao_integration.zentao_product_xid)
|
||||
end.to raise_error(Gitlab::Zentao::Client::Error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ping' do
|
||||
let(:mock_headers) do
|
||||
{
|
||||
headers: {
|
||||
'Content-Type' => 'application/json',
|
||||
'Token' => zentao_integration.api_token
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'with valid resource' do
|
||||
before do
|
||||
WebMock.stub_request(:get, mock_get_products_url)
|
||||
|
|
@ -102,4 +104,29 @@ RSpec.describe Gitlab::Zentao::Client do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#fetch_issue' do
|
||||
context 'with invalid id' do
|
||||
let(:invalid_ids) { ['story', 'story-', '-', '123', ''] }
|
||||
|
||||
it 'returns empty object' do
|
||||
invalid_ids.each do |id|
|
||||
expect { integration.fetch_issue(id) }.to raise_error(Gitlab::Zentao::Client::Error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid id' do
|
||||
let(:valid_ids) { %w[story-1 bug-23] }
|
||||
|
||||
it 'fetches current issue' do
|
||||
valid_ids.each do |id|
|
||||
WebMock.stub_request(:get, mock_fetch_issue_url(id))
|
||||
.with(mock_headers).to_return(status: 200, body: { issue: { id: id } }.to_json)
|
||||
|
||||
expect(integration.fetch_issue(id).dig('issue', 'id')).to eq id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Zentao::Query do
|
||||
let(:zentao_integration) { create(:zentao_integration) }
|
||||
let(:params) { {} }
|
||||
|
||||
subject(:query) { described_class.new(zentao_integration, ActionController::Parameters.new(params)) }
|
||||
|
||||
describe '#issues' do
|
||||
let(:response) { { 'page' => 1, 'total' => 0, 'limit' => 20, 'issues' => [] } }
|
||||
|
||||
def expect_query_option_include(expected_params)
|
||||
expect_next_instance_of(Gitlab::Zentao::Client) do |client|
|
||||
expect(client).to receive(:fetch_issues)
|
||||
.with(hash_including(expected_params))
|
||||
.and_return(response)
|
||||
end
|
||||
|
||||
query.issues
|
||||
end
|
||||
|
||||
context 'when params are empty' do
|
||||
it 'fills default params' do
|
||||
expect_query_option_include(status: 'opened', order: 'lastEditedDate_desc', labels: '')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when params contain valid options' do
|
||||
let(:params) { { state: 'closed', sort: 'created_asc', labels: %w[Bugs Features] } }
|
||||
|
||||
it 'fills params with standard of ZenTao' do
|
||||
expect_query_option_include(status: 'closed', order: 'openedDate_asc', labels: 'Bugs,Features')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when params contain invalid options' do
|
||||
let(:params) { { state: 'xxx', sort: 'xxx', labels: %w[xxx] } }
|
||||
|
||||
it 'fills default params with standard of ZenTao' do
|
||||
expect_query_option_include(status: 'opened', order: 'lastEditedDate_desc', labels: 'xxx')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#issue' do
|
||||
let(:response) { { 'issue' => { 'id' => 'story-1' } } }
|
||||
|
||||
before do
|
||||
expect_next_instance_of(Gitlab::Zentao::Client) do |client|
|
||||
expect(client).to receive(:fetch_issue)
|
||||
.and_return(response)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns issue object by client' do
|
||||
expect(query.issue).to include('id' => 'story-1')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Sidebars::Projects::Menus::ZentaoMenu do
|
||||
it_behaves_like 'ZenTao menu with CE version'
|
||||
end
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('add_timestamp_softwarelicensespolicy')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe AddTimestampSoftwarelicensespolicy do
|
||||
let(:software_licenses_policy) { table(:software_license_policies) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('backfill_project_settings')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe BackfillProjectSettings, :sidekiq, schema: 20200114113341 do
|
||||
let(:projects) { table(:projects) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('remove_invalid_jira_data')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe RemoveInvalidJiraData do
|
||||
let(:jira_tracker_data) { table(:jira_tracker_data) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('remove_invalid_issue_tracker_data')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe RemoveInvalidIssueTrackerData do
|
||||
let(:issue_tracker_data) { table(:issue_tracker_data) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('reschedule_migrate_issue_trackers_data')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe RescheduleMigrateIssueTrackersData do
|
||||
let(:services) { table(:services) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('remove_orphaned_chat_names')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe RemoveOrphanedChatNames, schema: 20200313202430 do
|
||||
let(:projects) { table(:projects) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('backfill_deployment_clusters_from_deployments')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe BackfillDeploymentClustersFromDeployments, :migration, :sidekiq, schema: 20200227140242 do
|
||||
describe '#up' do
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('change_variable_interpolation_format_in_common_metrics')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ChangeVariableInterpolationFormatInCommonMetrics, :migration do
|
||||
let(:prometheus_metrics) { table(:prometheus_metrics) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('dedup_mr_metrics')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe DedupMrMetrics, :migration, schema: 20200526013844 do
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('update_index_approval_rule_name_for_code_owners_rule_type')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe UpdateIndexApprovalRuleNameForCodeOwnersRuleType do
|
||||
let(:migration) { described_class.new }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('backfill_namespace_settings')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe BackfillNamespaceSettings, :sidekiq, schema: 20200703124823 do
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('adjust_unique_index_alert_management_alerts')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe AdjustUniqueIndexAlertManagementAlerts, :migration do
|
||||
let(:migration) { described_class.new }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('replace_unique_index_on_cycle_analytics_stages')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ReplaceUniqueIndexOnCycleAnalyticsStages, :migration, schema: 20200727142337 do
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('add_o_auth_paths_to_protected_paths')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe AddOAuthPathsToProtectedPaths do
|
||||
subject(:migration) { described_class.new }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
require_migration!('create_missing_vulnerabilities_issue_links')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe CreateMissingVulnerabilitiesIssueLinks, :migration do
|
||||
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('schedule_migration_to_hashed_storage')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleMigrationToHashedStorage, :sidekiq do
|
||||
describe '#up' do
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('create_initial_versions_for_pre_versioning_terraform_states')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe CreateInitialVersionsForPreVersioningTerraformStates do
|
||||
let(:namespace) { table(:namespaces).create!(name: 'terraform', path: 'terraform') }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('drop_backfill_jira_tracker_deployment_type_jobs')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe DropBackfillJiraTrackerDeploymentTypeJobs, :sidekiq, :redis, schema: 2020_10_14_205300 do
|
||||
subject(:migration) { described_class.new }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('migrate_services_to_http_integrations')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe MigrateServicesToHttpIntegrations do
|
||||
let!(:namespace) { table(:namespaces).create!(name: 'namespace', path: 'namespace') }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('backfill_jira_tracker_deployment_type2')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe BackfillJiraTrackerDeploymentType2, :sidekiq, schema: 20201028182809 do
|
||||
let(:services) { table(:services) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('cleanup_transfered_projects_shared_runners')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe CleanupTransferedProjectsSharedRunners, :sidekiq, schema: 20201110161542 do
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
require_migration!('schedule_remove_duplicate_vulnerabilities_findings')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleRemoveDuplicateVulnerabilitiesFindings, :migration do
|
||||
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('schedule_recalculate_uuid_on_vulnerabilities_occurrences')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleRecalculateUuidOnVulnerabilitiesOccurrences, :migration do
|
||||
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('remove_duplicate_services2')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe RemoveDuplicateServices2 do
|
||||
let_it_be(:namespaces) { table(:namespaces) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('alter_vsa_issue_first_mentioned_in_commit_value')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe AlterVsaIssueFirstMentionedInCommitValue, schema: 20210114033715 do
|
||||
let(:group_stages) { table(:analytics_cycle_analytics_group_stages) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('remove_bad_dependency_proxy_manifests')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe RemoveBadDependencyProxyManifests, schema: 20210128140157 do
|
||||
let_it_be(:namespaces) { table(:namespaces) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('backfill_updated_at_after_repository_storage_move')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe BackfillUpdatedAtAfterRepositoryStorageMove, :sidekiq do
|
||||
let_it_be(:projects) { table(:projects) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('add_environment_scope_to_group_variables')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe AddEnvironmentScopeToGroupVariables do
|
||||
let(:migration) { described_class.new }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('dedup_issue_metrics')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe DedupIssueMetrics, :migration, schema: 20210205104425 do
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('backfill_total_tuple_count_for_batched_migrations')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe BackfillTotalTupleCountForBatchedMigrations, :migration, schema: 20210406140057 do
|
||||
let_it_be(:table_name) { 'projects' }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!('reschedule_artifact_expiry_backfill_again')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe RescheduleArtifactExpiryBackfillAgain, :migration do
|
||||
let(:migration_class) { Gitlab::BackgroundMigration::BackfillArtifactExpiryDate }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('schedule_update_jira_tracker_data_deployment_type_based_on_url')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleUpdateJiraTrackerDataDeploymentTypeBasedOnUrl, :migration do
|
||||
let(:services_table) { table(:services) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('schedule_drop_invalid_vulnerabilities')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleDropInvalidVulnerabilities, :migration do
|
||||
let_it_be(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!('copy_adoption_snapshot_namespace')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe CopyAdoptionSnapshotNamespace, :migration, schema: 20210430124630 do
|
||||
let(:namespaces_table) { table(:namespaces) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!('copy_adoption_segments_namespace')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe CopyAdoptionSegmentsNamespace, :migration do
|
||||
let(:namespaces_table) { table(:namespaces) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!('add_project_value_stream_id_to_project_stages')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe AddProjectValueStreamIdToProjectStages, schema: 20210503105022 do
|
||||
let(:stages) { table(:analytics_cycle_analytics_project_stages) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('schedule_drop_invalid_vulnerabilities2')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleDropInvalidVulnerabilities2, :migration do
|
||||
let_it_be(:background_migration_jobs) { table(:background_migration_jobs) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('schedule_cleanup_orphaned_lfs_objects_projects')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleCleanupOrphanedLfsObjectsProjects, schema: 20210511165250 do
|
||||
let(:lfs_objects_projects) { table(:lfs_objects_projects) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('fix_total_stage_in_vsa')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe FixTotalStageInVsa, :migration, schema: 20210518001450 do
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('group_protected_environments_add_index_and_constraint')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe GroupProtectedEnvironmentsAddIndexAndConstraint do
|
||||
let(:migration) { described_class.new }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!('remove_builds_email_service_from_services')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe RemoveBuildsEmailServiceFromServices do
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!('delete_legacy_operations_feature_flags')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe DeleteLegacyOperationsFeatureFlags do
|
||||
let(:namespace) { table(:namespaces).create!(name: 'foo', path: 'bar') }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
require_migration!('cascade_delete_freeze_periods')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe CascadeDeleteFreezePeriods do
|
||||
let(:namespace) { table(:namespaces).create!(name: 'deploy_freeze', path: 'deploy_freeze') }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration! 'reschedule_merge_request_diff_users_background_migration'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe RescheduleMergeRequestDiffUsersBackgroundMigration, :migration do
|
||||
let(:migration) { described_class.new }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('update_issuable_slas_where_issue_closed')
|
||||
require_migration!
|
||||
|
||||
RSpec.describe UpdateIssuableSlasWhereIssueClosed, :migration do
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue