Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-08 03:09:54 +00:00
parent 2824b15286
commit ae69a88c2a
31 changed files with 873 additions and 58 deletions

View file

@ -259,8 +259,17 @@ export default {
);
}
},
expandedPanel: {
handler({ group, panel }) {
const dashboardPath = this.currentDashboard || this.firstDashboard.path;
updateHistory({
url: panelToUrl(dashboardPath, group, panel),
title: document.title,
});
},
deep: true,
},
},
created() {
this.setInitialState({
metricsEndpoint: this.metricsEndpoint,

View file

@ -1,4 +1,3 @@
import { pickBy } from 'lodash';
import { queryToObject, mergeUrlParams, removeParams } from '~/lib/utils/url_utility';
import {
timeRangeParamNames,
@ -174,25 +173,30 @@ export const expandedPanelPayloadFromUrl = (dashboard, search = window.location.
* Convert panel information to a URL for the user to
* bookmark or share highlighting a specific panel.
*
* @param {String} dashboardPath - Dashboard path used as identifier
* @param {String} group - Group Identifier
* If no group/panel is set, the dashboard URL is returned.
*
* @param {?String} dashboard - Dashboard path, used as identifier for a dashboard
* @param {?String} group - Group Identifier
* @param {?Object} panel - Panel object from the dashboard
* @param {?String} url - Base URL including current search params
* @returns Dashboard URL which expands a panel (chart)
*/
export const panelToUrl = (dashboardPath, group, panel, url = window.location.href) => {
if (!group || !panel) {
return null;
export const panelToUrl = (dashboard = null, group, panel, url = window.location.href) => {
const params = {
dashboard,
};
if (group && panel) {
params.group = group;
params.title = panel.title;
params.y_label = panel.y_label;
} else {
// Remove existing parameters if any
params.group = null;
params.title = null;
params.y_label = null;
}
const params = pickBy(
{
dashboard: dashboardPath,
group,
title: panel.title,
y_label: panel.y_label,
},
value => value != null,
);
return mergeUrlParams(params, url);
};

View file

@ -683,6 +683,8 @@ module Ci
variables.concat(merge_request.predefined_variables)
end
variables.append(key: 'CI_KUBERNETES_ACTIVE', value: 'true') if has_kubernetes_active?
if external_pull_request_event? && external_pull_request
variables.concat(external_pull_request.predefined_variables)
end

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
class AccessibilityErrorEntity < Grape::Entity
expose :code
expose :type
expose :typeCode, as: :type_code
expose :message
expose :context
expose :selector
expose :runner
expose :runnerExtras, as: :runner_extras
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AccessibilityReportsComparerEntity < Grape::Entity
expose :status
expose :new_errors, using: AccessibilityErrorEntity
expose :resolved_errors, using: AccessibilityErrorEntity
expose :existing_errors, using: AccessibilityErrorEntity
expose :summary do
expose :total_count, as: :total
expose :resolved_count, as: :resolved
expose :errors_count, as: :errored
end
end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
class AccessibilityReportsComparerSerializer < BaseSerializer
entity AccessibilityReportsComparerEntity
end

View file

@ -0,0 +1,5 @@
---
title: Update metrics dashboard url when a panel is expanded or contracted
merge_request: 30704
author:
type: added

View file

@ -0,0 +1,6 @@
---
title: Add a CI variable CI_KUBERNETES_ACTIVE as an alternative to only:kubernetes/except:kubernetes
that works with the rules syntax
merge_request: 31146
author:
type: added

View file

@ -167,6 +167,44 @@ do this manually.
previously for the **secondary**.
1. Success! The **secondary** has now been promoted to **primary**.
#### Promoting a **secondary** node with an external PostgreSQL database
The `gitlab-ctl promote-to-primary-node` command cannot be used in conjunction with
an external PostgreSQL database, as it can only perform changes on a **secondary**
node with GitLab and the database on the same machine. As a result, a manual process is
required. For example, PostgreSQL databases hosted on Amazon RDS:
1. Promote the replica database associated with the **secondary** site. This will
set the database to read-write:
- Amazon RDS - [Promoting a Read Replica to Be a Standalone DB Instance](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ReadRepl.html#USER_ReadRepl.Promote)
1. Edit `/etc/gitlab/gitlab.rb` on every node in the **secondary** site to
reflect its new status as **primary** by removing any lines that enabled the
`geo_secondary_role`:
```ruby
## In GitLab 11.4 and earlier, remove this line.
geo_secondary_role['enable'] = true
## In GitLab 11.5 and later, remove this line.
roles ['geo_secondary_role']
```
After making these changes [Reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure)
on each node so the changes take effect.
1. Promote the **secondary** to **primary**. SSH into a single secondary application
node and execute:
```shell
sudo gitlab-rake geo:set_secondary_as_primary
```
1. Verify you can connect to the newly promoted **primary** site using the URL used
previously for the **secondary** site.
Success! The **secondary** site has now been promoted to **primary**.
### Step 4. (Optional) Updating the primary domain DNS record
Updating the DNS records for the primary domain to point to the **secondary** node

View file

@ -64,6 +64,7 @@ future GitLab releases.**
| `CI_JOB_TOKEN` | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry](../../user/packages/container_registry/index.md) and downloading [dependent repositories](../../user/project/new_ci_build_permissions_model.md#dependent-repositories) |
| `CI_JOB_JWT` | 12.10 | all | RS256 JSON web token that can be used for authenticating with third party systems that support JWT authentication, for example [HashiCorp's Vault](../examples/authenticating-with-hashicorp-vault). |
| `CI_JOB_URL` | 11.1 | 0.5 | Job details URL |
| `CI_KUBERNETES_ACTIVE` | 13.0 | all | Included with the value `true` only if the pipeline has a Kubernetes cluster available for deployments. Not included if no cluster is availble. Can be used as an alternative to [`only:kubernetes`/`except:kubernetes`](../yaml/README.md#onlykubernetesexceptkubernetes) with [`rules:if`](../yaml/README.md#rulesif) |
| `CI_MERGE_REQUEST_ASSIGNEES` | 11.9 | all | Comma-separated list of username(s) of assignee(s) for the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
| `CI_MERGE_REQUEST_CHANGED_PAGE_PATHS` | 12.9 | all | Comma-separated list of paths of changed pages in a deployed [Review App](../review_apps/index.md) for a [Merge Request](../merge_request_pipelines/index.md). A [Route Map](../review_apps/index.md#route-maps) must be configured. |
| `CI_MERGE_REQUEST_CHANGED_PAGE_URLS` | 12.9 | all | Comma-separated list of URLs of changed pages in a deployed [Review App](../review_apps/index.md) for a [Merge Request](../merge_request_pipelines/index.md). A [Route Map](../review_apps/index.md#route-maps) must be configured. |

View file

@ -171,8 +171,39 @@ Adding or removing a NOT NULL clause (or another constraint) can typically be
done without requiring downtime. However, this does require that any application
changes are deployed _first_. Thus, changing the constraints of a column should
happen in a post-deployment migration.
NOTE: Avoid using `change_column` as it produces inefficient query because it re-defines
the whole column type. For example, to add a NOT NULL constraint, prefer `change_column_null`
NOTE: Avoid using `change_column` as it produces an inefficient query because it re-defines
the whole column type.
To add a NOT NULL constraint, use the `add_not_null_constraint` migration helper:
```ruby
# A post-deployment migration in db/post_migrate
class AddNotNull < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
add_not_null_constraint :users, :username
end
def down
remove_not_null_constraint :users, :username
end
end
```
If the column to be updated requires cleaning first (e.g. there are `NULL` values), you should:
1. Add the `NOT NULL` constraint with `validate: false`
`add_not_null_constraint :users, :username, validate: false`
1. Clean up the data with a data migration
1. Validate the `NOT NULL` constraint with a followup migration
`validate_not_null_constraint :users, :username`
## Changing Column Types

View file

@ -338,3 +338,77 @@ To fix this issue, you can either:
[Learn more on overriding the SAST template](sast/index.md#overriding-the-sast-template).
All the security scanning tools define their stage, so this error can occur with all of them.
### Getting error message `sast job: config key may not be used with 'rules': only/except`
When including a security job template like [`SAST`](sast/index.md#overriding-the-sast-template),
the following error may occur, depending on your GitLab CI/CD configuration:
```plaintext
Found errors in your .gitlab-ci.yml:
jobs:sast config key may not be used with `rules`: only/except
```
This error appears when the included job's `rules` configuration has been [overridden](sast/index.md#overriding-the-sast-template)
with [the deprecated `only` or `except` syntax.](../../ci/yaml/README.md#onlyexcept-basic)
To fix this issue, you must either:
- [Transition your `only/except` syntax to `rules`](#transitioning-your-onlyexcept-syntax-to-rules).
- (Temporarily) [Pin your templates to the deprecated versions](#pin-your-templates-to-the-deprecated-versions)
[Learn more on overriding the SAST template](sast/index.md#overriding-the-sast-template).
#### Transitioning your `only/except` syntax to `rules`
When overriding the template to control job execution, previous instances of
[`only` or `except`](../../ci/yaml/README.md#onlyexcept-basic) are no longer compatible
and must be transitioned to [the `rules` syntax](../../ci/yaml/README.md#rules).
If your override is aimed at limiting jobs to only run on `master`, the previous syntax
would look similar to:
```yaml
include:
- template: SAST.gitlab-ci.yml
# Ensure that the scanning is only executed on master or merge requests
spotbugs-sast:
only:
refs:
- master
- merge_requests
```
To transition the above configuration to the new `rules` syntax, the override
would be written as follows:
```yaml
include:
- template: SAST.gitlab-ci.yml
# Ensure that the scanning is only executed on master or merge requests
spotbugs-sast:
rules:
- if: $CI_COMMIT_BRANCH == "master"
- if: $CI_MERGE_REQUEST_ID
```
[Learn more on the usage of `rules`](../../ci/yaml/README.md#rules).
#### Pin your templates to the deprecated versions
To ensure the latest support, we **strongly** recommend that you migrate to [`rules`](../../ci/yaml/README.md#rules).
If you're unable to immediately update your CI configuration, there are several workarounds that
involve pinning to the previous template versions, for example:
```yaml
include:
remote: 'https://gitlab.com/gitlab-org/gitlab/-/raw/12-10-stable-ee/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml'
```
Additionally, we provide a dedicated project containing the versioned legacy templates.
This can be useful for offline setups or anyone wishing to use [Auto DevOps](../../topics/autodevops/index.md)..
Instructions are available in the [legacy template project](https://gitlab.com/gitlab-org/auto-devops-v12-10).

View file

@ -6,7 +6,7 @@ module Gitlab
module Accessibility
class Pa11y
def parse!(json_data, accessibility_report)
root = Gitlab::Json.parse(json_data)
root = Gitlab::Json.parse(json_data).with_indifferent_access
parse_all(root, accessibility_report)
rescue JSON::ParserError => e

View file

@ -265,12 +265,19 @@ module Gitlab
# or `RESET ALL` is executed
def disable_statement_timeout
if block_given?
begin
execute('SET statement_timeout TO 0')
if statement_timeout_disabled?
# Don't do anything if the statement_timeout is already disabled
# Allows for nested calls of disable_statement_timeout without
# resetting the timeout too early (before the outer call ends)
yield
ensure
execute('RESET ALL')
else
begin
execute('SET statement_timeout TO 0')
yield
ensure
execute('RESET ALL')
end
end
else
unless transaction_open?
@ -495,7 +502,7 @@ module Gitlab
update_column_in_batches(table, column, default_after_type_cast, &block)
end
change_column_null(table, column, false) unless allow_null
add_not_null_constraint(table, column) unless allow_null
# We want to rescue _all_ exceptions here, even those that don't inherit
# from StandardError.
rescue Exception => error # rubocop: disable all
@ -1334,12 +1341,73 @@ into similar problems in the future (e.g. when new tables are created).
check_constraint_exists?(table, text_limit_name(table, column, name: constraint_name))
end
# Migration Helpers for managing not null constraints
def add_not_null_constraint(table, column, constraint_name: nil, validate: true)
if column_is_nullable?(table, column)
add_check_constraint(
table,
"#{column} IS NOT NULL",
not_null_constraint_name(table, column, name: constraint_name),
validate: validate
)
else
warning_message = <<~MESSAGE
NOT NULL check constraint was not created:
column #{table}.#{column} is already defined as `NOT NULL`
MESSAGE
Rails.logger.warn warning_message
end
end
def validate_not_null_constraint(table, column, constraint_name: nil)
validate_check_constraint(
table,
not_null_constraint_name(table, column, name: constraint_name)
)
end
def remove_not_null_constraint(table, column, constraint_name: nil)
remove_check_constraint(
table,
not_null_constraint_name(table, column, name: constraint_name)
)
end
def check_not_null_constraint_exists?(table, column, constraint_name: nil)
check_constraint_exists?(
table,
not_null_constraint_name(table, column, name: constraint_name)
)
end
private
def statement_timeout_disabled?
# This is a string of the form "100ms" or "0" when disabled
connection.select_value('SHOW statement_timeout') == "0"
end
def column_is_nullable?(table, column)
# Check if table.column has not been defined with NOT NULL
check_sql = <<~SQL
SELECT c.is_nullable
FROM information_schema.columns c
WHERE c.table_name = '#{table}'
AND c.column_name = '#{column}'
SQL
connection.select_value(check_sql) == 'YES'
end
def text_limit_name(table, column, name: nil)
name.presence || check_constraint_name(table, column, 'max_length')
end
def not_null_constraint_name(table, column, name: nil)
name.presence || check_constraint_name(table, column, 'not_null')
end
def missing_schema_object_message(table, type, name)
<<~MESSAGE
Could not find #{type} "#{name}" on table "#{table}" which was referenced during the migration.
@ -1383,7 +1451,7 @@ into similar problems in the future (e.g. when new tables are created).
update_column_in_batches(table, new, Arel::Table.new(table)[old], batch_column_name: batch_column_name)
change_column_null(table, new, false) unless old_col.null
add_not_null_constraint(table, new) unless old_col.null
copy_indexes(table, old, new)
copy_foreign_keys(table, old, new)

View file

@ -1,8 +1,8 @@
# frozen_string_literal: true
module QA
context 'Create' do
describe 'Gitaly repository storage', :orchestrated, :repository_storage, :requires_admin, quarantine: { type: :new } do
context 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217002', type: :investigating } do
describe 'Gitaly repository storage', :orchestrated, :repository_storage, :requires_admin do
let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let(:parent_project) do
Resource::Project.fabricate_via_api! do |project|

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
context 'Create', quarantine: { type: :new } do
context 'Create' do
describe 'Review a merge request in Web IDE' do
let(:new_file) { 'awesome_new_file.txt' }
let(:original_text) { 'Text' }

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
context 'Release', :docker, quarantine: { type: :new } do
context 'Release', :docker do
describe 'Parent-child pipelines dependent relationship' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
context 'Release', :docker, quarantine: { type: :new } do
context 'Release', :docker do
describe 'Parent-child pipelines independent relationship' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|

View file

@ -1,8 +1,8 @@
# frozen_string_literal: true
module QA
context 'Configure' do
describe 'Kubernetes Cluster Integration', :orchestrated, :kubernetes, :requires_admin, quarantine: { type: :new } do
context 'Configure', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/209085', type: :investigating } do
describe 'Kubernetes Cluster Integration', :orchestrated, :kubernetes, :requires_admin do
context 'Project Clusters' do
let(:cluster) { Service::KubernetesCluster.new(provider_class: Service::ClusterProvider::K3s).create! }
let(:project) do

View file

@ -2,7 +2,7 @@
module QA
context 'Monitor' do
describe 'Dashboards', :orchestrated, :kubernetes, quarantine: { type: :new } do
describe 'Dashboards', :orchestrated, :kubernetes, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29262', type: :waiting_on } do
before(:all) do
@cluster = Service::KubernetesCluster.new.create!
Flow::Login.sign_in

View file

@ -0,0 +1,40 @@
{
"type": "object",
"required": [
"code",
"type",
"type_code",
"message",
"context",
"selector",
"runner",
"runner_extras"
],
"properties": {
"code": {
"type": "string"
},
"type": {
"type": "string"
},
"type_code": {
"type": "integer"
},
"message": {
"type": "string"
},
"context": {
"type": "string"
},
"selector": {
"type": "string"
},
"runner": {
"type": "string"
},
"runner_extras": {
"type": ["object", "null"]
}
},
"additionalProperties": false
}

View file

@ -0,0 +1,43 @@
{
"type": "object",
"required": ["status", "summary", "new_errors", "resolved_errors", "existing_errors"],
"properties": {
"status": {
"type": "string"
},
"summary": {
"type": "object",
"properties": {
"total": {
"type": "integer"
},
"resolved": {
"type": "integer"
},
"errored": {
"type": "integer"
}
},
"required": ["total", "resolved", "errored"]
},
"new_errors": {
"type": "array",
"items": {
"$ref": "accessibility_error.json"
}
},
"resolved_errors": {
"type": "array",
"items": {
"$ref": "accessibility_error.json"
}
},
"existing_errors": {
"type": "array",
"items": {
"$ref": "accessibility_error.json"
}
}
},
"additionalProperties": false
}

View file

@ -234,6 +234,90 @@ describe('Dashboard', () => {
});
});
describe('when the panel is expanded', () => {
let group;
let panel;
const expandPanel = (mockGroup, mockPanel) => {
store.commit(`monitoringDashboard/${types.SET_EXPANDED_PANEL}`, {
group: mockGroup,
panel: mockPanel,
});
};
beforeEach(() => {
setupStoreWithData(store);
const { panelGroups } = store.state.monitoringDashboard.dashboard;
group = panelGroups[0].group;
[panel] = panelGroups[0].panels;
jest.spyOn(window.history, 'pushState').mockImplementation();
});
afterEach(() => {
window.history.pushState.mockRestore();
});
it('URL is updated with panel parameters', () => {
createMountedWrapper({ hasMetrics: true });
expandPanel(group, panel);
const expectedSearch = objectToQuery({
group,
title: panel.title,
y_label: panel.y_label,
});
return wrapper.vm.$nextTick(() => {
expect(window.history.pushState).toHaveBeenCalledTimes(1);
expect(window.history.pushState).toHaveBeenCalledWith(
expect.anything(), // state
expect.any(String), // document title
expect.stringContaining(`?${expectedSearch}`),
);
});
});
it('URL is updated with panel parameters and custom dashboard', () => {
const dashboard = 'dashboard.yml';
createMountedWrapper({ hasMetrics: true, currentDashboard: dashboard });
expandPanel(group, panel);
const expectedSearch = objectToQuery({
dashboard,
group,
title: panel.title,
y_label: panel.y_label,
});
return wrapper.vm.$nextTick(() => {
expect(window.history.pushState).toHaveBeenCalledTimes(1);
expect(window.history.pushState).toHaveBeenCalledWith(
expect.anything(), // state
expect.any(String), // document title
expect.stringContaining(`?${expectedSearch}`),
);
});
});
it('URL is updated with no parameters', () => {
expandPanel(group, panel);
createMountedWrapper({ hasMetrics: true });
expandPanel(null, null);
return wrapper.vm.$nextTick(() => {
expect(window.history.pushState).toHaveBeenCalledTimes(1);
expect(window.history.pushState).toHaveBeenCalledWith(
expect.anything(), // state
expect.any(String), // document title
expect.not.stringContaining('?'), // no params
);
});
});
});
describe('when all requests have been commited by the store', () => {
beforeEach(() => {
createMountedWrapper({ hasMetrics: true });

View file

@ -274,9 +274,10 @@ describe('monitoring/utils', () => {
const [panelGroup] = metricsDashboardViewModel.panelGroups;
const [panel] = panelGroup.panels;
const getUrlParams = url => urlUtils.queryToObject(url.split('?')[1]);
it('returns URL for a panel when query parameters are given', () => {
const [, query] = panelToUrl(dashboard, panelGroup.group, panel).split('?');
const params = urlUtils.queryToObject(query);
const params = getUrlParams(panelToUrl(dashboard, panelGroup.group, panel));
expect(params).toEqual({
dashboard,
@ -286,12 +287,14 @@ describe('monitoring/utils', () => {
});
});
it('returns `null` if group is missing', () => {
expect(panelToUrl(dashboard, null, panel)).toBe(null);
it('returns a dashboard only URL if group is missing', () => {
const params = getUrlParams(panelToUrl(dashboard, null, panel));
expect(params).toEqual({ dashboard: 'metrics.yml' });
});
it('returns `null` if panel is missing', () => {
expect(panelToUrl(dashboard, panelGroup.group, null)).toBe(null);
it('returns a dashboard only URL if panel is missing', () => {
const params = getUrlParams(panelToUrl(dashboard, panelGroup.group, null));
expect(params).toEqual({ dashboard: 'metrics.yml' });
});
});

View file

@ -88,6 +88,7 @@ describe Gitlab::Ci::Parsers::Accessibility::Pa11y do
expect(accessibility_report.passes_count).to eq(0)
expect(accessibility_report.scans_count).to eq(1)
expect(accessibility_report.urls['https://about.gitlab.com/']).to be_present
expect(accessibility_report.urls['https://about.gitlab.com/'].first[:code]).to be_present
end
end
end

View file

@ -217,9 +217,10 @@ describe Gitlab::Database::MigrationHelpers do
it 'appends ON DELETE SET NULL statement' do
expect(model).to receive(:with_lock_retries).and_call_original
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).with(/RESET ALL/)
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
expect(model).to receive(:execute).with(/ON DELETE SET NULL/)
@ -233,9 +234,10 @@ describe Gitlab::Database::MigrationHelpers do
it 'appends ON DELETE CASCADE statement' do
expect(model).to receive(:with_lock_retries).and_call_original
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).with(/RESET ALL/)
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
expect(model).to receive(:execute).with(/ON DELETE CASCADE/)
@ -249,9 +251,10 @@ describe Gitlab::Database::MigrationHelpers do
it 'appends no ON DELETE statement' do
expect(model).to receive(:with_lock_retries).and_call_original
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).with(/RESET ALL/)
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
expect(model).not_to receive(:execute).with(/ON DELETE/)
@ -266,10 +269,11 @@ describe Gitlab::Database::MigrationHelpers do
it 'creates a concurrent foreign key and validates it' do
expect(model).to receive(:with_lock_retries).and_call_original
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).with(/RESET ALL/)
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
end
@ -293,10 +297,11 @@ describe Gitlab::Database::MigrationHelpers do
it 'creates a new foreign key' do
expect(model).to receive(:with_lock_retries).and_call_original
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+foo/)
expect(model).to receive(:execute).with(/RESET ALL/)
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo)
end
@ -321,10 +326,11 @@ describe Gitlab::Database::MigrationHelpers do
it 'creates a new foreign key' do
expect(model).to receive(:with_lock_retries).and_call_original
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+bar/)
expect(model).to receive(:execute).with(/RESET ALL/)
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :bar)
end
@ -361,6 +367,7 @@ describe Gitlab::Database::MigrationHelpers do
aggregate_failures do
expect(model).not_to receive(:concurrent_foreign_key_name)
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/ALTER TABLE projects VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
@ -377,6 +384,7 @@ describe Gitlab::Database::MigrationHelpers do
aggregate_failures do
expect(model).to receive(:concurrent_foreign_key_name)
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/ALTER TABLE projects VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
@ -527,6 +535,26 @@ describe Gitlab::Database::MigrationHelpers do
end
end
end
# This spec runs without an enclosing transaction (:delete truncation method for db_cleaner)
context 'when the statement_timeout is already disabled', :delete do
before do
ActiveRecord::Base.connection.execute('SET statement_timeout TO 0')
end
after do
# Use ActiveRecord::Base.connection instead of model.execute
# so that this call is not counted below
ActiveRecord::Base.connection.execute('RESET ALL')
end
it 'yields control without disabling the timeout or resetting' do
expect(model).not_to receive(:execute).with('SET statement_timeout TO 0')
expect(model).not_to receive(:execute).with('RESET ALL')
expect { |block| model.disable_statement_timeout(&block) }.to yield_control
end
end
end
describe '#true_value' do
@ -619,7 +647,7 @@ describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:update_column_in_batches)
.with(:projects, :foo, 10)
expect(model).not_to receive(:change_column_null)
expect(model).not_to receive(:add_not_null_constraint)
model.add_column_with_default(:projects, :foo, :integer,
default: 10,
@ -630,8 +658,8 @@ describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:update_column_in_batches)
.with(:projects, :foo, 10)
expect(model).to receive(:change_column_null)
.with(:projects, :foo, false)
expect(model).to receive(:add_not_null_constraint)
.with(:projects, :foo)
model.add_column_with_default(:projects, :foo, :integer, default: 10)
end
@ -650,16 +678,16 @@ describe Gitlab::Database::MigrationHelpers do
end
it 'removes the added column whenever changing a column NULL constraint fails' do
expect(model).to receive(:change_column_null)
.with(:projects, :foo, false)
.and_raise(RuntimeError)
expect(model).to receive(:add_not_null_constraint)
.with(:projects, :foo)
.and_raise(ActiveRecord::ActiveRecordError)
expect(model).to receive(:remove_column)
.with(:projects, :foo)
expect do
model.add_column_with_default(:projects, :foo, :integer, default: 10)
end.to raise_error(RuntimeError)
end.to raise_error(ActiveRecord::ActiveRecordError)
end
end
@ -671,7 +699,7 @@ describe Gitlab::Database::MigrationHelpers do
allow(model).to receive(:transaction).and_yield
allow(model).to receive(:column_for).with(:user_details, :foo).and_return(column)
allow(model).to receive(:update_column_in_batches).with(:user_details, :foo, 10, batch_column_name: :user_id)
allow(model).to receive(:change_column_null).with(:user_details, :foo, false)
allow(model).to receive(:add_not_null_constraint).with(:user_details, :foo)
allow(model).to receive(:change_column_default).with(:user_details, :foo, 10)
expect(model).to receive(:add_column)
@ -693,7 +721,7 @@ describe Gitlab::Database::MigrationHelpers do
allow(model).to receive(:transaction).and_yield
allow(model).to receive(:column_for).with(:projects, :foo).and_return(column)
allow(model).to receive(:update_column_in_batches).with(:projects, :foo, 10)
allow(model).to receive(:change_column_null).with(:projects, :foo, false)
allow(model).to receive(:add_not_null_constraint).with(:projects, :foo)
allow(model).to receive(:change_column_default).with(:projects, :foo, 10)
expect(model).to receive(:add_column)
@ -782,7 +810,7 @@ describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:update_column_in_batches)
expect(model).to receive(:change_column_null).with(:users, :new, false)
expect(model).to receive(:add_not_null_constraint).with(:users, :new)
expect(model).to receive(:copy_indexes).with(:users, :old, :new)
expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
@ -915,7 +943,7 @@ describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:update_column_in_batches)
expect(model).to receive(:change_column_null).with(:users, :old, false)
expect(model).to receive(:add_not_null_constraint).with(:users, :old)
expect(model).to receive(:copy_indexes).with(:users, :new, :old)
expect(model).to receive(:copy_foreign_keys).with(:users, :new, :old)
@ -2225,6 +2253,7 @@ describe Gitlab::Database::MigrationHelpers do
.and_return(false).exactly(1)
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:with_lock_retries).and_call_original
expect(model).to receive(:execute).with(/ADD CONSTRAINT check_name_not_null/)
@ -2268,6 +2297,7 @@ describe Gitlab::Database::MigrationHelpers do
.and_return(false).exactly(1)
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:with_lock_retries).and_call_original
expect(model).to receive(:execute).with(/ADD CONSTRAINT check_name_not_null/)
@ -2309,6 +2339,7 @@ describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:check_constraint_exists?).and_return(true)
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(validate_sql)
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
@ -2448,4 +2479,135 @@ describe Gitlab::Database::MigrationHelpers do
end
end
end
describe '#add_not_null_constraint' do
context 'when it is called with the default options' do
it 'calls add_check_constraint with an infered constraint name and validate: true' do
constraint_name = model.check_constraint_name(:test_table,
:name,
'not_null')
check = "name IS NOT NULL"
expect(model).to receive(:column_is_nullable?).and_return(true)
expect(model).to receive(:check_constraint_name).and_call_original
expect(model).to receive(:add_check_constraint)
.with(:test_table, check, constraint_name, validate: true)
model.add_not_null_constraint(:test_table, :name)
end
end
context 'when all parameters are provided' do
it 'calls add_check_constraint with the correct parameters' do
constraint_name = 'check_name_not_null'
check = "name IS NOT NULL"
expect(model).to receive(:column_is_nullable?).and_return(true)
expect(model).not_to receive(:check_constraint_name)
expect(model).to receive(:add_check_constraint)
.with(:test_table, check, constraint_name, validate: false)
model.add_not_null_constraint(
:test_table,
:name,
constraint_name: constraint_name,
validate: false
)
end
end
context 'when the column is defined as NOT NULL' do
it 'does not add a check constraint' do
expect(model).to receive(:column_is_nullable?).and_return(false)
expect(model).not_to receive(:check_constraint_name)
expect(model).not_to receive(:add_check_constraint)
model.add_not_null_constraint(:test_table, :name)
end
end
end
describe '#validate_not_null_constraint' do
context 'when constraint_name is not provided' do
it 'calls validate_check_constraint with an infered constraint name' do
constraint_name = model.check_constraint_name(:test_table,
:name,
'not_null')
expect(model).to receive(:check_constraint_name).and_call_original
expect(model).to receive(:validate_check_constraint)
.with(:test_table, constraint_name)
model.validate_not_null_constraint(:test_table, :name)
end
end
context 'when constraint_name is provided' do
it 'calls validate_check_constraint with the correct parameters' do
constraint_name = 'check_name_not_null'
expect(model).not_to receive(:check_constraint_name)
expect(model).to receive(:validate_check_constraint)
.with(:test_table, constraint_name)
model.validate_not_null_constraint(:test_table, :name, constraint_name: constraint_name)
end
end
end
describe '#remove_not_null_constraint' do
context 'when constraint_name is not provided' do
it 'calls remove_check_constraint with an infered constraint name' do
constraint_name = model.check_constraint_name(:test_table,
:name,
'not_null')
expect(model).to receive(:check_constraint_name).and_call_original
expect(model).to receive(:remove_check_constraint)
.with(:test_table, constraint_name)
model.remove_not_null_constraint(:test_table, :name)
end
end
context 'when constraint_name is provided' do
it 'calls remove_check_constraint with the correct parameters' do
constraint_name = 'check_name_not_null'
expect(model).not_to receive(:check_constraint_name)
expect(model).to receive(:remove_check_constraint)
.with(:test_table, constraint_name)
model.remove_not_null_constraint(:test_table, :name, constraint_name: constraint_name)
end
end
end
describe '#check_not_null_constraint_exists?' do
context 'when constraint_name is not provided' do
it 'calls check_constraint_exists? with an infered constraint name' do
constraint_name = model.check_constraint_name(:test_table,
:name,
'not_null')
expect(model).to receive(:check_constraint_name).and_call_original
expect(model).to receive(:check_constraint_exists?)
.with(:test_table, constraint_name)
model.check_not_null_constraint_exists?(:test_table, :name)
end
end
context 'when constraint_name is provided' do
it 'calls check_constraint_exists? with the correct parameters' do
constraint_name = 'check_name_not_null'
expect(model).not_to receive(:check_constraint_name)
expect(model).to receive(:check_constraint_exists?)
.with(:test_table, constraint_name)
model.check_not_null_constraint_exists?(:test_table, :name, constraint_name: constraint_name)
end
end
end
end

View file

@ -719,6 +719,28 @@ describe Ci::Pipeline, :mailer do
)
end
end
describe 'variable CI_KUBERNETES_ACTIVE' do
context 'when pipeline.has_kubernetes_active? is true' do
before do
allow(pipeline).to receive(:has_kubernetes_active?).and_return(true)
end
it "is incldued with value 'true'" do
expect(subject.to_hash).to include('CI_KUBERNETES_ACTIVE' => 'true')
end
end
context 'when pipeline.has_kubernetes_active? is false' do
before do
allow(pipeline).to receive(:has_kubernetes_active?).and_return(false)
end
it 'is not included' do
expect(subject.to_hash).not_to have_key('CI_KUBERNETES_ACTIVE')
end
end
end
end
describe '#protected_ref?' do

View file

@ -0,0 +1,37 @@
# frozen_string_literal: true
require 'spec_helper'
describe AccessibilityErrorEntity do
let(:entity) { described_class.new(accessibility_error) }
describe '#as_json' do
subject { entity.as_json }
context 'when accessibility contains an error' do
let(:accessibility_error) do
{
code: "WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent",
type: "error",
typeCode: 1,
message: "Anchor element found with a valid href attribute, but no link content has been supplied.",
context: "<a href=\"/\" class=\"navbar-brand animated\"><svg height=\"36\" viewBox=\"0 0 1...</a>",
selector: "#main-nav > div:nth-child(1) > a",
runner: "htmlcs",
runnerExtras: {}
}
end
it 'contains correct accessibility error details', :aggregate_failures do
expect(subject[:code]).to eq("WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent")
expect(subject[:type]).to eq("error")
expect(subject[:type_code]).to eq(1)
expect(subject[:message]).to eq("Anchor element found with a valid href attribute, but no link content has been supplied.")
expect(subject[:context]).to eq("<a href=\"/\" class=\"navbar-brand animated\"><svg height=\"36\" viewBox=\"0 0 1...</a>")
expect(subject[:selector]).to eq("#main-nav > div:nth-child(1) > a")
expect(subject[:runner]).to eq("htmlcs")
expect(subject[:runner_extras]).to be_empty
end
end
end
end

View file

@ -0,0 +1,87 @@
# frozen_string_literal: true
require 'spec_helper'
describe AccessibilityReportsComparerEntity do
let(:entity) { described_class.new(comparer) }
let(:comparer) { Gitlab::Ci::Reports::AccessibilityReportsComparer.new(base_report, head_report) }
let(:base_report) { Gitlab::Ci::Reports::AccessibilityReports.new }
let(:head_report) { Gitlab::Ci::Reports::AccessibilityReports.new }
let(:url) { "https://gitlab.com" }
let(:single_error) do
[
{
code: "WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent",
type: "error",
typeCode: 1,
message: "Anchor element found with a valid href attribute, but no link content has been supplied.",
context: "<a href=\"/\" class=\"navbar-brand animated\"><svg height=\"36\" viewBox=\"0 0 1...</a>",
selector: "#main-nav > div:nth-child(1) > a",
runner: "htmlcs",
runnerExtras: {}
}
]
end
let(:different_error) do
[
{
code: "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail",
type: "error",
typeCode: 1,
message: "This element has insufficient contrast at this conformance level.",
context: "<a href=\"/stages-devops-lifecycle/\" class=\"main-nav-link\">Product</a>",
selector: "#main-nav > div:nth-child(2) > ul > li:nth-child(1) > a",
runner: "htmlcs",
runnerExtras: {}
}
]
end
describe '#as_json' do
subject { entity.as_json }
context 'when base report has error and head has a different error' do
before do
base_report.add_url(url, single_error)
head_report.add_url(url, different_error)
end
it 'contains correct compared accessibility report details', :aggregate_failures do
expect(subject[:status]).to eq(Gitlab::Ci::Reports::AccessibilityReportsComparer::STATUS_FAILED)
expect(subject[:resolved_errors].first).to include(:code, :type, :type_code, :message, :context, :selector, :runner, :runner_extras)
expect(subject[:new_errors].first).to include(:code, :type, :type_code, :message, :context, :selector, :runner, :runner_extras)
expect(subject[:existing_errors].first).to include(:code, :type, :type_code, :message, :context, :selector, :runner, :runner_extras)
expect(subject[:summary]).to include(total: 2, resolved: 1, errored: 1)
end
end
context 'when base report has error and head has the same error' do
before do
base_report.add_url(url, single_error)
head_report.add_url(url, single_error)
end
it 'contains correct compared accessibility report details', :aggregate_failures do
expect(subject[:status]).to eq(Gitlab::Ci::Reports::AccessibilityReportsComparer::STATUS_FAILED)
expect(subject[:new_errors]).to be_empty
expect(subject[:resolved_errors]).to be_empty
expect(subject[:existing_errors].first).to include(:code, :type, :type_code, :message, :context, :selector, :runner, :runner_extras)
expect(subject[:summary]).to include(total: 1, resolved: 0, errored: 1)
end
end
context 'when base report has no error and head has errors' do
before do
head_report.add_url(url, single_error)
end
it 'contains correct compared accessibility report details', :aggregate_failures do
expect(subject[:status]).to eq(Gitlab::Ci::Reports::AccessibilityReportsComparer::STATUS_FAILED)
expect(subject[:resolved_errors]).to be_empty
expect(subject[:existing_errors]).to be_empty
expect(subject[:new_errors].first).to include(:code, :type, :type_code, :message, :context, :selector, :runner, :runner_extras)
expect(subject[:summary]).to include(total: 1, resolved: 0, errored: 1)
end
end
end
end

View file

@ -0,0 +1,65 @@
# frozen_string_literal: true
require 'spec_helper'
describe AccessibilityReportsComparerSerializer do
let(:project) { double(:project) }
let(:serializer) { described_class.new(project: project).represent(comparer) }
let(:comparer) { Gitlab::Ci::Reports::AccessibilityReportsComparer.new(base_report, head_report) }
let(:base_report) { Gitlab::Ci::Reports::AccessibilityReports.new }
let(:head_report) { Gitlab::Ci::Reports::AccessibilityReports.new }
let(:url) { "https://gitlab.com" }
let(:single_error) do
[
{
code: "WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent",
type: "error",
typeCode: 1,
message: "Anchor element found with a valid href attribute, but no link content has been supplied.",
context: "<a href=\"/\" class=\"navbar-brand animated\"><svg height=\"36\" viewBox=\"0 0 1...</a>",
selector: "#main-nav > divnth-child(1) > a",
runner: "htmlcs",
runnerExtras: {}
}
]
end
let(:different_error) do
[
{
code: "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail",
type: "error",
typeCode: 1,
message: "This element has insufficient contrast at this conformance level.",
context: "<a href=\"/stages-devops-lifecycle/\" class=\"main-nav-link\">Product</a>",
selector: "#main-nav > divnth-child(2) > ul > linth-child(1) > a",
runner: "htmlcs",
runnerExtras: {}
}
]
end
describe '#to_json' do
subject { serializer.as_json }
context 'when base report has error and head has a different error' do
before do
base_report.add_url(url, single_error)
head_report.add_url(url, different_error)
end
it 'matches the schema' do
expect(subject).to match_schema('entities/accessibility_reports_comparer')
end
end
context 'when base report has no error and head has errors' do
before do
head_report.add_url(url, single_error)
end
it 'matches the schema' do
expect(subject).to match_schema('entities/accessibility_reports_comparer')
end
end
end
end

View file

@ -13,10 +13,11 @@ end
RSpec.shared_examples 'performs validation' do |validation_option|
it 'performs validation' do
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:statement_timeout_disabled?).and_return(false)
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).with(/RESET ALL/)
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
model.add_concurrent_foreign_key(*args, **options.merge(validation_option))
end