Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-04-06 09:08:18 +00:00
parent c5d8b7e690
commit eb4b72630a
84 changed files with 1279 additions and 443 deletions

View File

@ -1281,7 +1281,6 @@ lib/gitlab/checks/** @proglottis @toon
/app/workers/stuck_ci_jobs_worker.rb
/app/workers/update_external_pull_requests_worker.rb
/lib/api/commit_statuses.rb
/ee/app/models/merge_train.rb
/ee/app/finders/merge_trains_finder.rb
/ee/app/services/auto_merge/add_to_merge_train_when_pipeline_succeeds_service.rb
/ee/app/services/auto_merge/merge_train_service.rb
@ -1290,7 +1289,6 @@ lib/gitlab/checks/** @proglottis @toon
/ee/app/controllers/ee/projects/pipelines_controller.rb
/ee/app/controllers/projects/pipelines/
/ee/app/controllers/projects/subscriptions_controller.rb
/ee/app/models/merge_train.rb
/ee/app/helpers/ee/projects/pipeline_helper.rb
/ee/app/views/ci_minutes_usage_mailer/
/ee/app/views/projects/pipelines/
@ -1334,7 +1332,6 @@ lib/gitlab/checks/** @proglottis @toon
/spec/workers/run_pipeline_schedule_worker_spec.rb
/spec/workers/stuck_ci_jobs_worker_spec.rb
/spec/workers/update_external_pull_requests_worker_spec.rb
/ee/spec/models/merge_train_spec.rb
/ee/spec/finders/merge_trains_finder_spec.rb
/ee/spec/services/auto_merge/add_to_merge_train_when_pipeline_succeeds_service_spec.rb
/ee/spec/services/auto_merge/merge_train_service_spec.rb

View File

@ -30,7 +30,6 @@ Fips/SHA1:
- 'ee/spec/lib/gitlab/ci/reports/security/locations/dast_spec.rb'
- 'ee/spec/lib/gitlab/ci/reports/security/locations/dependency_scanning_spec.rb'
- 'ee/spec/migrations/update_vulnerability_occurrences_location_spec.rb'
- 'ee/spec/models/merge_train_spec.rb'
- 'ee/spec/models/resource_weight_event_spec.rb'
- 'ee/spec/models/vulnerabilities/finding_signature_spec.rb'
- 'ee/spec/models/vulnerabilities/finding_spec.rb'

View File

@ -923,7 +923,6 @@ Gitlab/NamespacedClass:
- 'ee/app/models/license.rb'
- 'ee/app/models/merge_request_block.rb'
- 'ee/app/models/merge_request_diff_detail.rb'
- 'ee/app/models/merge_train.rb'
- 'ee/app/models/namespace_limit.rb'
- 'ee/app/models/path_lock.rb'
- 'ee/app/models/productivity_analytics.rb'

View File

@ -1338,14 +1338,6 @@ Layout/ArgumentAlignment:
- 'ee/spec/graphql/types/pipeline_security_report_finding_type_spec.rb'
- 'ee/spec/graphql/types/project_type_spec.rb'
- 'ee/spec/graphql/types/vulnerability_type_spec.rb'
- 'ee/spec/helpers/billing_plans_helper_spec.rb'
- 'ee/spec/helpers/ee/integrations_helper_spec.rb'
- 'ee/spec/helpers/ee/namespace_user_cap_reached_alert_helper_spec.rb'
- 'ee/spec/helpers/ee/namespaces_helper_spec.rb'
- 'ee/spec/helpers/ee/trial_registration_helper_spec.rb'
- 'ee/spec/helpers/license_monitoring_helper_spec.rb'
- 'ee/spec/helpers/projects_helper_spec.rb'
- 'ee/spec/helpers/vulnerabilities_helper_spec.rb'
- 'ee/spec/lib/analytics/group_activity_calculator_spec.rb'
- 'ee/spec/lib/analytics/merge_request_metrics_calculator_spec.rb'
- 'ee/spec/lib/api/entities/protected_environments/approval_rule_for_summary_spec.rb'
@ -1640,7 +1632,6 @@ Layout/ArgumentAlignment:
- 'ee/spec/services/vulnerabilities/user_notes_count_service_spec.rb'
- 'ee/spec/services/vulnerability_feedback/create_service_spec.rb'
- 'ee/spec/services/vulnerability_merge_request_links/create_service_spec.rb'
- 'ee/spec/support/helpers/vulnerability_helpers.rb'
- 'ee/spec/support/shared_examples/audit/audit_event_type_stream_shared_examples.rb'
- 'ee/spec/support/shared_examples/controllers/analytics/cycle_analytics/shared_stage_shared_examples.rb'
- 'ee/spec/support/shared_examples/features/credentials_inventory_shared_examples.rb'
@ -2173,18 +2164,6 @@ Layout/ArgumentAlignment:
- 'spec/graphql/types/project_type_spec.rb'
- 'spec/graphql/types/root_storage_statistics_type_spec.rb'
- 'spec/graphql/types/todo_type_spec.rb'
- 'spec/helpers/avatars_helper_spec.rb'
- 'spec/helpers/emoji_helper_spec.rb'
- 'spec/helpers/feature_flags_helper_spec.rb'
- 'spec/helpers/namespaces_helper_spec.rb'
- 'spec/helpers/notify_helper_spec.rb'
- 'spec/helpers/page_layout_helper_spec.rb'
- 'spec/helpers/routing/pseudonymization_helper_spec.rb'
- 'spec/helpers/storage_helper_spec.rb'
- 'spec/helpers/todos_helper_spec.rb'
- 'spec/helpers/users/callouts_helper_spec.rb'
- 'spec/helpers/users/group_callouts_helper_spec.rb'
- 'spec/helpers/visibility_level_helper_spec.rb'
- 'spec/initializers/00_rails_disable_joins_spec.rb'
- 'spec/initializers/secret_token_spec.rb'
- 'spec/lib/api/every_api_endpoint_spec.rb'
@ -2756,13 +2735,6 @@ Layout/ArgumentAlignment:
- 'spec/services/work_items/task_list_reference_removal_service_spec.rb'
- 'spec/services/work_items/widgets/description_service/update_service_spec.rb'
- 'spec/sidekiq/cron/job_gem_dependency_spec.rb'
- 'spec/support/helpers/api_internal_base_helpers.rb'
- 'spec/support/helpers/board_helpers.rb'
- 'spec/support/helpers/ci/source_pipeline_helpers.rb'
- 'spec/support/helpers/feature_flag_helpers.rb'
- 'spec/support/helpers/graphql_helpers.rb'
- 'spec/support/helpers/stub_object_storage.rb'
- 'spec/support/helpers/workhorse_helpers.rb'
- 'spec/support/import_export/export_file_helper.rb'
- 'spec/support/redis/redis_shared_examples.rb'
- 'spec/support/shared_contexts/bulk_imports_requests_shared_context.rb'

View File

@ -2117,7 +2117,6 @@ Layout/LineLength:
- 'ee/spec/models/merge_request_spec.rb'
- 'ee/spec/models/merge_requests/compliance_violation_spec.rb'
- 'ee/spec/models/merge_requests/external_status_check_spec.rb'
- 'ee/spec/models/merge_train_spec.rb'
- 'ee/spec/models/namespace_setting_spec.rb'
- 'ee/spec/models/note_spec.rb'
- 'ee/spec/models/packages/package_file_spec.rb'

View File

@ -223,7 +223,6 @@ Layout/SpaceInLambdaLiteral:
- 'ee/app/models/iterations/cadence.rb'
- 'ee/app/models/merge_request_block.rb'
- 'ee/app/models/merge_requests/compliance_violation.rb'
- 'ee/app/models/merge_train.rb'
- 'ee/app/models/namespaces/namespace_ban.rb'
- 'ee/app/models/requirements_management/requirement.rb'
- 'ee/app/models/resource_iteration_event.rb'

View File

@ -499,7 +499,6 @@ RSpec/ContextWording:
- 'ee/spec/models/member_spec.rb'
- 'ee/spec/models/merge_request/blocking_spec.rb'
- 'ee/spec/models/merge_request_spec.rb'
- 'ee/spec/models/merge_train_spec.rb'
- 'ee/spec/models/namespace_setting_spec.rb'
- 'ee/spec/models/note_spec.rb'
- 'ee/spec/models/packages/package_file_spec.rb'

View File

@ -26,7 +26,6 @@ RSpec/DescribedClass:
- 'ee/spec/models/issue_spec.rb'
- 'ee/spec/models/iteration_spec.rb'
- 'ee/spec/models/license_spec.rb'
- 'ee/spec/models/merge_train_spec.rb'
- 'ee/spec/models/project_import_state_spec.rb'
- 'ee/spec/models/release_highlight_spec.rb'
- 'ee/spec/models/requirements_management/test_report_spec.rb'

View File

@ -17,7 +17,6 @@ RSpec/HooksBeforeExamples:
- 'ee/spec/lib/ee/gitlab/usage_data_counters/hll_redis_counter_spec.rb'
- 'ee/spec/lib/gitlab/analytics/cycle_analytics/summary/group/stage_summary_spec.rb'
- 'ee/spec/models/ee/merge_request_diff_spec.rb'
- 'ee/spec/models/merge_train_spec.rb'
- 'ee/spec/requests/api/boards_spec.rb'
- 'ee/spec/requests/ee/projects/deploy_tokens_controller_spec.rb'
- 'ee/spec/services/ee/groups/deploy_tokens/create_service_spec.rb'

View File

@ -1254,7 +1254,6 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/models/merge_requests/compliance_violation_spec.rb'
- 'ee/spec/models/merge_requests/external_status_check_spec.rb'
- 'ee/spec/models/merge_requests/status_check_response_spec.rb'
- 'ee/spec/models/merge_train_spec.rb'
- 'ee/spec/models/milestone_release_spec.rb'
- 'ee/spec/models/milestone_spec.rb'
- 'ee/spec/models/namespace_limit_spec.rb'

View File

@ -1 +1 @@
d7ad67347247776ec267d4f2056e2c4cffcf4ebd
c31b9fed97bb01a1790496386ceab8e31e76b1d8

View File

@ -140,6 +140,19 @@ export default {
this.infiniteScrollingTriggered = false;
this.filterSearchTriggered = true;
// all filters have been cleared reset query param
// and refetch jobs/count with defaults
if (!filters.length) {
updateHistory({
url: setUrlParams({ statuses: null }, window.location.href, true),
});
this.$apollo.queries.jobs.refetch({ statuses: null });
this.$apollo.queries.jobsCount.refetch({ statuses: null });
return;
}
// Eventually there will be more tokens available
// this code is written to scale for those tokens
filters.forEach((filter) => {

View File

@ -0,0 +1,106 @@
<script>
import { GlButton, GlModal } from '@gitlab/ui';
import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue';
import {
CONFIRM_MODAL,
CONFIRM_MODAL_TITLE,
COPY_SECRET,
DESCRIPTION_SECRET,
RENEW_SECRET,
RENEW_SECRET_FAILURE,
RENEW_SECRET_SUCCESS,
WARNING_NO_SECRET,
} from '../constants';
export default {
CONFIRM_MODAL,
CONFIRM_MODAL_TITLE,
COPY_SECRET,
DESCRIPTION_SECRET,
RENEW_SECRET,
name: 'OAuthSecret',
components: {
GlButton,
GlModal,
InputCopyToggleVisibility,
},
inject: ['initialSecret', 'renewPath'],
data() {
return {
secret: this.initialSecret,
alert: null,
isModalVisible: false,
isLoading: false,
};
},
computed: {
actionPrimary() {
return {
text: this.$options.RENEW_SECRET,
attributes: {
variant: 'confirm',
loading: this.isLoading,
},
};
},
},
created() {
if (!this.secret) {
this.alert = createAlert({ message: WARNING_NO_SECRET, variant: VARIANT_WARNING });
}
},
methods: {
displayModal() {
this.isModalVisible = true;
},
async renewSecret(event) {
event.preventDefault();
this.isLoading = true;
this.alert?.dismiss();
try {
const { data } = await axios.put(this.renewPath);
this.alert = createAlert({ message: RENEW_SECRET_SUCCESS, variant: VARIANT_SUCCESS });
this.secret = data.secret;
} catch {
this.alert = createAlert({ message: RENEW_SECRET_FAILURE });
} finally {
this.isLoading = false;
this.isModalVisible = false;
}
},
},
};
</script>
<template>
<div class="gl-display-flex gl-flex-wrap-wrap gl-gap-5">
<input-copy-toggle-visibility
v-if="secret"
:copy-button-title="$options.COPY_SECRET"
:value="secret"
class="gl-mt-n3 gl-mb-0"
>
<template #description>
{{ $options.DESCRIPTION_SECRET }}
</template>
</input-copy-toggle-visibility>
<gl-button category="secondary" class="gl-align-self-start" @click="displayModal">{{
$options.RENEW_SECRET
}}</gl-button>
<gl-modal
v-model="isModalVisible"
:title="$options.CONFIRM_MODAL_TITLE"
size="sm"
modal-id="modal-renew-secret"
:action-primary="actionPrimary"
@primary="renewSecret"
>
{{ $options.CONFIRM_MODAL }}
</gl-modal>
</div>
</template>

View File

@ -0,0 +1,20 @@
import { __, s__ } from '~/locale';
export const CONFIRM_MODAL = s__(
'AuthorizedApplication|Are you sure you want to renew this secret? Any applications using the old secret will no longer be able to authenticate with GitLab.',
);
export const CONFIRM_MODAL_TITLE = s__('AuthorizedApplication|Renew secret?');
export const COPY_SECRET = __('Copy secret');
export const DESCRIPTION_SECRET = __(
'This is the only time the secret is accessible. Copy the secret and store it securely.',
);
export const RENEW_SECRET = s__('AuthorizedApplication|Renew secret');
export const RENEW_SECRET_FAILURE = s__(
'AuthorizedApplication|There was an error trying to renew the application secret. Please try again.',
);
export const RENEW_SECRET_SUCCESS = s__(
'AuthorizedApplication|Application secret was successfully renewed.',
);
export const WARNING_NO_SECRET = __(
'The secret is only available when you create the application or renew the secret.',
);

View File

@ -0,0 +1,21 @@
import Vue from 'vue';
import OAuthSecret from './components/oauth_secret.vue';
export const initOAuthApplicationSecret = () => {
const el = document.querySelector('#js-oauth-application-secret');
if (!el) {
return null;
}
const { initialSecret, renewPath } = el.dataset;
return new Vue({
el,
name: 'OAuthSecretRoot',
provide: { initialSecret, renewPath },
render(h) {
return h(OAuthSecret);
},
});
};

View File

@ -1,3 +1,5 @@
import initApplicationDeleteButtons from '~/admin/applications';
import { initOAuthApplicationSecret } from '~/oauth_application';
initApplicationDeleteButtons();
initOAuthApplicationSecret();

View File

@ -0,0 +1,3 @@
import { initOAuthApplicationSecret } from '~/oauth_application';
initOAuthApplicationSecret();

View File

@ -0,0 +1,3 @@
import { initOAuthApplicationSecret } from '~/oauth_application';
initOAuthApplicationSecret();

View File

@ -47,10 +47,9 @@ class Admin::ApplicationsController < Admin::ApplicationController
@application.renew_secret
if @application.save
flash.now[:notice] = s_('AuthorizedApplication|Application secret was successfully updated.')
render :show
render json: { secret: @application.plaintext_secret }
else
redirect_to admin_application_url(@application)
render json: { errors: @application.errors }, status: :unprocessable_entity
end
end

View File

@ -46,10 +46,9 @@ module Groups
@application.renew_secret
if @application.save
flash.now[:notice] = s_('AuthorizedApplication|Application secret was successfully updated.')
render :show
render json: { secret: @application.plaintext_secret }
else
redirect_to group_settings_application_url(@group, @application)
render json: { errors: @application.errors }, status: :unprocessable_entity
end
end

View File

@ -45,10 +45,9 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
@application.renew_secret
if @application.save
flash.now[:notice] = s_('AuthorizedApplication|Application secret was successfully updated.')
render :show
render json: { secret: @application.plaintext_secret }
else
redirect_to oauth_application_url(@application)
render json: { errors: @application.errors }, status: :unprocessable_entity
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
module Types
module Ci
module Catalog
# rubocop: disable Graphql/AuthorizeTypes
class ResourceType < BaseObject
graphql_name 'CiCatalogResource'
connection_type_class(Types::CountableConnectionType)
field :id, GraphQL::Types::ID, null: false, description: 'ID of the catalog resource.',
alpha: { milestone: '15.11' }
field :name, GraphQL::Types::String, null: true, description: 'Name of the catalog resource.',
alpha: { milestone: '15.11' }
field :description, GraphQL::Types::String, null: true, description: 'Description of the catalog resource.',
alpha: { milestone: '15.11' }
field :icon, GraphQL::Types::String, null: true, description: 'Icon for the catalog resource.',
method: :avatar_path, alpha: { milestone: '15.11' }
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
end

View File

@ -13,6 +13,8 @@ module Ci
belongs_to :project
scope :for_projects, ->(project_ids) { where(project_id: project_ids) }
delegate :avatar_path, :description, :name, to: :project
end
end
end

View File

@ -15,16 +15,7 @@
%td
= _('Secret')
%td
- if @application.plaintext_secret
= render Pajamas::AlertComponent.new(variant: :warning, dismissible: false, alert_options: { class: 'gl-mb-5'}) do |c|
= c.body do
= _('This is the only time the secret is accessible. Copy the secret and store it securely.')
= clipboard_button(clipboard_text: @application.plaintext_secret, button_text: _('Copy'), title: _("Copy secret"), class: "btn btn-default btn-md gl-button")
- else
= render Pajamas::AlertComponent.new(variant: :warning, dismissible: false, alert_options: { class: 'gl-mb-5'}) do |c|
= c.body do
= _('The secret is only available when you create the application or renew the secret.')
= render 'shared/doorkeeper/applications/update_form', path: renew_path
#js-oauth-application-secret{ data: { initial_secret: @application.plaintext_secret, renew_path: renew_path } }
%tr
%td

View File

@ -1,3 +0,0 @@
- path = local_assigns.fetch(:path)
= form_for(@application, url: path, html: {class: 'gl-display-inline-block', method: "put"}) do |f|
= submit_tag s_('AuthorizedApplication|Renew secret'), data: { confirm: s_("AuthorizedApplication|Are you sure you want to renew this secret? Any applications using the old secret will no longer be able to authenticate with GitLab."), confirm_btn_variant: "danger" }, aria: { label: s_('AuthorizedApplication|Renew secret') }, class: 'gl-button btn btn-md btn-default'

View File

@ -1,10 +1,10 @@
---
table_name: merge_trains
classes:
- MergeTrain
- MergeTrains::Car
feature_categories:
- continuous_integration
description: TODO
description: Each record represents a single merge request which is or was part of a merge train.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/827fc3ccb9335aa29fba0fc532b70015ec4c5186
milestone: '11.11'
gitlab_schema: gitlab_main

View File

@ -5,4 +5,6 @@ feature_categories:
description: The schema_inconsistencies table contains a list of database schema inconsistencies.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114876
milestone: '15.11'
classes:
- Gitlab::Database::SchemaValidation::SchemaInconsistency
gitlab_schema: gitlab_main

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class TruncatePCiRunnerMachineBuilds < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
return unless Gitlab::Database.gitlab_schemas_for_connection(connection).include?(:gitlab_ci)
execute('TRUNCATE TABLE p_ci_runner_machine_builds')
end
# no-op
def down; end
end

View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
class SwapCiRunnerMachineBuildsPrimaryKeyV2 < Gitlab::Database::Migration[2.1]
include Gitlab::Database::PartitioningMigrationHelpers
disable_ddl_transaction!
TABLE_NAME = :p_ci_runner_machine_builds
BUILDS_TABLE = :ci_builds
def up
reorder_primary_key_columns([:build_id, :partition_id])
end
def down
reorder_primary_key_columns([:partition_id, :build_id])
end
private
def reorder_primary_key_columns(columns)
with_lock_retries(raise_on_exhaustion: true) do
connection.execute(<<~SQL)
LOCK TABLE #{BUILDS_TABLE}, #{TABLE_NAME} IN ACCESS EXCLUSIVE MODE;
SQL
partitions = Gitlab::Database::PostgresPartitionedTable.each_partition(TABLE_NAME).to_a
partitions.each { |partition| drop_table partition.identifier }
execute <<~SQL
ALTER TABLE #{TABLE_NAME}
DROP CONSTRAINT p_ci_runner_machine_builds_pkey CASCADE;
ALTER TABLE #{TABLE_NAME}
ADD PRIMARY KEY (#{columns.join(', ')});
SQL
partitions.each do |partition|
connection.execute(<<~SQL)
CREATE TABLE IF NOT EXISTS #{partition.identifier}
PARTITION OF #{partition.parent_identifier} #{partition.condition};
SQL
end
end
end
end

View File

@ -0,0 +1 @@
4ca98e9c93245a8fc1f4124d00d47d73d12b961affde1d53b7262ffc93582d83

View File

@ -0,0 +1 @@
a85e3139d843295e666867129575818f61983a8b16eaa73f9b470e394d9c5476

View File

@ -27376,7 +27376,7 @@ ALTER TABLE ONLY operations_user_lists
ADD CONSTRAINT operations_user_lists_pkey PRIMARY KEY (id);
ALTER TABLE ONLY p_ci_runner_machine_builds
ADD CONSTRAINT p_ci_runner_machine_builds_pkey PRIMARY KEY (partition_id, build_id);
ADD CONSTRAINT p_ci_runner_machine_builds_pkey PRIMARY KEY (build_id, partition_id);
ALTER TABLE ONLY packages_build_infos
ADD CONSTRAINT packages_build_infos_pkey PRIMARY KEY (id);

View File

@ -55,6 +55,26 @@ CI related settings that apply to the entire instance.
Returns [`CiApplicationSettings`](#ciapplicationsettings).
### `Query.ciCatalogResources`
CI Catalog resources visible to the current user.
WARNING:
**Introduced** in 15.11.
This feature is in Alpha. It can be changed or removed at any time.
Returns [`CiCatalogResourceConnection`](#cicatalogresourceconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="querycicatalogresourcesprojectpath"></a>`projectPath` | [`ID`](#id) | Project with the namespace catalog. |
### `Query.ciConfig`
Linted and processed contents of a CI config.
@ -7114,6 +7134,30 @@ The edge type for [`CiBuildNeed`](#cibuildneed).
| <a id="cibuildneededgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="cibuildneededgenode"></a>`node` | [`CiBuildNeed`](#cibuildneed) | The item at the end of the edge. |
#### `CiCatalogResourceConnection`
The connection type for [`CiCatalogResource`](#cicatalogresource).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cicatalogresourceconnectioncount"></a>`count` | [`Int!`](#int) | Total count of collection. |
| <a id="cicatalogresourceconnectionedges"></a>`edges` | [`[CiCatalogResourceEdge]`](#cicatalogresourceedge) | A list of edges. |
| <a id="cicatalogresourceconnectionnodes"></a>`nodes` | [`[CiCatalogResource]`](#cicatalogresource) | A list of nodes. |
| <a id="cicatalogresourceconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `CiCatalogResourceEdge`
The edge type for [`CiCatalogResource`](#cicatalogresource).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cicatalogresourceedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="cicatalogresourceedgenode"></a>`node` | [`CiCatalogResource`](#cicatalogresource) | The item at the end of the edge. |
#### `CiConfigGroupConnection`
The connection type for [`CiConfigGroup`](#ciconfiggroup).
@ -11678,6 +11722,17 @@ Represents the total number of issues and their weights for a particular day.
| <a id="cibuildneedid"></a>`id` | [`ID!`](#id) | ID of the BuildNeed. |
| <a id="cibuildneedname"></a>`name` | [`String`](#string) | Name of the job we need to complete. |
### `CiCatalogResource`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cicatalogresourcedescription"></a>`description` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.11. This feature is in Alpha. It can be changed or removed at any time. Description of the catalog resource. |
| <a id="cicatalogresourceicon"></a>`icon` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.11. This feature is in Alpha. It can be changed or removed at any time. Icon for the catalog resource. |
| <a id="cicatalogresourceid"></a>`id` **{warning-solid}** | [`ID!`](#id) | **Introduced** in 15.11. This feature is in Alpha. It can be changed or removed at any time. ID of the catalog resource. |
| <a id="cicatalogresourcename"></a>`name` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.11. This feature is in Alpha. It can be changed or removed at any time. Name of the catalog resource. |
### `CiConfig`
#### Fields

View File

@ -4,13 +4,18 @@ group: Pipeline Execution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Visual Review discussions API **(PREMIUM)**
<!--- start_remove The following content will be removed on remove_date: '2024-05-22' -->
# Visual Review discussions API (deprecated) **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18710) in GitLab 12.5.
> - [Moved](https://about.gitlab.com/blog/2021/01/26/new-gitlab-product-subscription-model/) to GitLab Premium in 13.9.
WARNING:
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/387751) in GitLab 15.8
and is planned for removal in 17.0. This change is a breaking change.
Visual Review discussions are notes on merge requests sent as
feedback from [Visual Reviews](../ci/review_apps/index.md#visual-reviews).
feedback from [Visual Reviews](../ci/review_apps/index.md#visual-reviews-deprecated).
## Create new merge request thread
@ -45,3 +50,4 @@ Parameters:
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/merge_requests/11/visual_review_discussions?body=comment"
```
<!--- end_remove -->

View File

@ -193,13 +193,18 @@ After you have the route mapping set up, it takes effect in the following locati
![View on environment button in file view](img/view_on_env_blob.png)
## Visual Reviews **(PREMIUM)**
<!--- start_remove The following content will be removed on remove_date: '2024-05-22' -->
## Visual Reviews (deprecated) **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10761) in GitLab 12.0.
> - [Moved](https://about.gitlab.com/blog/2021/01/26/new-gitlab-product-subscription-model/) to GitLab Premium in 13.9.
> - It's [deployed behind a feature flag](../../user/feature_flags.md), `anonymous_visual_review_feedback`, disabled by default.
> - It's disabled on GitLab.com.
WARNING:
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/387751) in GitLab 15.8
and is planned for removal in 17.0. This change is a breaking change.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available,
ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `anonymous_visual_review_feedback`.
@ -305,3 +310,5 @@ the user must enter a [personal access token](../../user/profile/personal_access
with `api` scope before submitting feedback.
This same method can be used to require authentication for any public projects.
<!--- end_remove -->

View File

@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25144) in GitLab 12.8.
WARNING:
This feature was deprecated in GitLab 15.9
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/390424) in GitLab 15.9
and is planned for removal in 17.0. This change is a breaking change.
If your application offers a web interface, you can use

View File

@ -4,10 +4,15 @@ group: Pipeline Execution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
<!--- start_remove The following content will be removed on remove_date: '2024-05-22' -->
# Browser Performance Testing **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3507) in GitLab 10.3.
WARNING:
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/388719) in GitLab 15.9
and is planned for removal in 17.0. This change is a breaking change.
If your application offers a web interface and you're using
[GitLab CI/CD](../index.md), you can quickly determine the rendering performance
impact of pending code changes in the browser.

View File

@ -4,10 +4,15 @@ group: Pipeline Execution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Load Performance Testing **(PREMIUM)**
<!--- start_remove The following content will be removed on remove_date: '2024-05-22' -->
# Load Performance Testing (deprecated) **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10683) in GitLab 13.2.
WARNING:
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/388723) in GitLab 15.9
and is planned for removal in 17.0. This change is a breaking change.
With Load Performance Testing, you can test the impact of any pending code changes
to your application's backend in [GitLab CI/CD](../index.md).
@ -199,3 +204,5 @@ load_performance:
rules:
- if: $CI_COMMIT_BRANCH # Modify to match your pipeline rules, or use `only/except` if needed.
```
<!--- end_remove -->

View File

@ -224,7 +224,7 @@ Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
curl --remote-name --location --progress-bar "https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.5.tar.gz"
echo '9afc6380a027a4fe1ae1a3e2eccb6b497b9c5ac0631c12ca56f9b7beb4848776 ruby-3.0.5.tar.gz' | sha256sum -c - && tar xzf ruby-3.0.5.tar.gz
cd ruby-2.7.6
cd ruby-3.0.5
./configure --disable-install-rdoc --enable-shared
make

View File

@ -71,7 +71,7 @@ the tiers are no longer mentioned in GitLab documentation:
- [Code Owners as eligible approvers](../user/project/merge_requests/approvals/rules.md#code-owners-as-eligible-approvers)
- [Approval rules](../user/project/merge_requests/approvals/rules.md) features
- [Restricting push and merge access to certain users](../user/project/protected_branches.md)
- [Visual Reviews](../ci/review_apps/index.md#visual-reviews)
- [Visual Reviews (deprecated)](../ci/review_apps/index.md#visual-reviews-deprecated)
- Metrics and analytics:
- [Contribution Analytics](../user/group/contribution_analytics/index.md)
- [Merge Request Analytics](../user/analytics/merge_request_analytics.md)

View File

@ -91,7 +91,7 @@ sent and subject to your payment terms.
- You purchased your subscription from a reseller or another channel partner.
- You purchased a multi-year subscription.
- You purchased your subscription with a purchasing order.
- You are a pubic sector customer.
- You are a public sector customer.
- You have an offline environment and used a license file to activate your subscription.
- You are enrolled in a program that provides a free tier such as the GitLab for Education, GitLab for Open Source Program, or GitLab for Startups.

View File

@ -27,7 +27,7 @@
# batch_sum(User, :sign_in_count)
# batch_sum(Issue.group(:state_id), :weight))
# batch_average(Ci::Pipeline, :duration)
# batch_average(MergeTrain.group(:status), :duration)
# batch_average(MergeTrains::Car.group(:status), :duration)
module Gitlab
module Database
module BatchCount

View File

@ -18,6 +18,10 @@ module Gitlab
validator_class.name.demodulize.underscore
end
def table_name
structure_sql_object&.table_name || database_object&.table_name
end
def object_name
structure_sql_object&.name || database_object&.name
end

View File

@ -1,25 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Database
module SchemaValidation
class Index
def initialize(parsed_stmt)
@parsed_stmt = parsed_stmt
end
def name
parsed_stmt.idxname
end
def statement
@statement ||= PgQuery.deparse_stmt(parsed_stmt)
end
private
attr_reader :parsed_stmt
end
end
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Gitlab
module Database
module SchemaValidation
class SchemaInconsistency < ApplicationRecord
self.table_name = :schema_inconsistencies
belongs_to :issue
validates :object_name, :valitador_name, :table_name, presence: true
end
end
end
end

View File

@ -13,6 +13,10 @@ module Gitlab
raise NoMethodError, "subclasses of #{self.class.name} must implement #{__method__}"
end
def table_name
parsed_stmt.relation.relname
end
def statement
@statement ||= PgQuery.deparse_stmt(parsed_stmt)
end

View File

@ -0,0 +1,77 @@
# frozen_string_literal: true
module Gitlab
module Database
module SchemaValidation
class TrackInconsistency
def initialize(inconsistency, project, user)
@inconsistency = inconsistency
@project = project
@user = user
end
def execute
return unless Gitlab.com?
return if inconsistency_record.present?
result = ::Issues::CreateService.new(container: project, current_user: user, params: params,
spam_params: nil).execute
track_inconsistency(result[:issue]) if result.success?
end
private
attr_reader :inconsistency, :project, :user
def track_inconsistency(issue)
schema_inconsistency_model.create(
issue: issue,
object_name: inconsistency.object_name,
table_name: inconsistency.table_name,
valitador_name: inconsistency.type
)
end
def params
{
title: issue_title,
description: issue_description,
confidential: true,
issue_type: 'issue',
labels: %w[database database-inconsistency-report]
}
end
def issue_title
"New schema inconsistency: #{inconsistency.object_name}"
end
def issue_description
<<~MSG
We have detected a new schema inconsistency.
Table_name: #{inconsistency.table_name}
Object_name: #{inconsistency.object_name}
Validator_name: #{inconsistency.type}
Error_message: #{inconsistency.error_message}
For more information, please contact the database team.
MSG
end
def schema_inconsistency_model
Gitlab::Database::SchemaValidation::SchemaInconsistency
end
def inconsistency_record
schema_inconsistency_model.find_by(
object_name: inconsistency.object_name,
table_name: inconsistency.table_name,
valitador_name: inconsistency.type
)
end
end
end
end
end

View File

@ -455,7 +455,15 @@ namespace :gitlab do
inconsistencies = Gitlab::Database::SchemaValidation::Runner.new(structure_sql, database).execute
gitlab_url = 'gitlab-org/gitlab'
inconsistencies.each do |inconsistency|
Gitlab::Database::SchemaValidation::TrackInconsistency.new(
inconsistency,
Project.find_by_full_path(gitlab_url),
User.support_bot
).execute
puts inconsistency.inspect
end
end

View File

@ -6180,7 +6180,7 @@ msgstr ""
msgid "Authorized applications (%{size})"
msgstr ""
msgid "AuthorizedApplication|Application secret was successfully updated."
msgid "AuthorizedApplication|Application secret was successfully renewed."
msgstr ""
msgid "AuthorizedApplication|Are you sure you want to renew this secret? Any applications using the old secret will no longer be able to authenticate with GitLab."
@ -6192,9 +6192,15 @@ msgstr ""
msgid "AuthorizedApplication|Renew secret"
msgstr ""
msgid "AuthorizedApplication|Renew secret?"
msgstr ""
msgid "AuthorizedApplication|Revoke application"
msgstr ""
msgid "AuthorizedApplication|There was an error trying to renew the application secret. Please try again."
msgstr ""
msgid "Authors: %{authors}"
msgstr ""

View File

@ -50,6 +50,12 @@ RSpec.describe Admin::ApplicationsController do
it { is_expected.to have_gitlab_http_status(:ok) }
it { expect { subject }.to change { application.reload.secret } }
it 'returns the secret in json format' do
subject
expect(json_response['secret']).not_to be_nil
end
context 'when renew fails' do
before do
allow_next_found_instance_of(Doorkeeper::Application) do |application|
@ -58,7 +64,7 @@ RSpec.describe Admin::ApplicationsController do
end
it { expect { subject }.not_to change { application.reload.secret } }
it { is_expected.to redirect_to(admin_application_url(application)) }
it { is_expected.to have_gitlab_http_status(:unprocessable_entity) }
end
end

View File

@ -156,6 +156,12 @@ RSpec.describe Groups::Settings::ApplicationsController do
it { is_expected.to have_gitlab_http_status(:ok) }
it { expect { subject }.to change { application.reload.secret } }
it 'returns the secret in json format' do
subject
expect(json_response['secret']).not_to be_nil
end
context 'when renew fails' do
before do
allow_next_found_instance_of(Doorkeeper::Application) do |application|
@ -164,7 +170,7 @@ RSpec.describe Groups::Settings::ApplicationsController do
end
it { expect { subject }.not_to change { application.reload.secret } }
it { is_expected.to redirect_to(group_settings_application_url(group, application)) }
it { is_expected.to have_gitlab_http_status(:unprocessable_entity) }
end
end

View File

@ -86,6 +86,12 @@ RSpec.describe Oauth::ApplicationsController do
it_behaves_like 'redirects to login page when the user is not signed in'
it_behaves_like 'redirects to 2fa setup page when the user requires it'
it 'returns the secret in json format' do
subject
expect(json_response['secret']).not_to be_nil
end
context 'when renew fails' do
before do
allow_next_found_instance_of(Doorkeeper::Application) do |application|
@ -94,7 +100,7 @@ RSpec.describe Oauth::ApplicationsController do
end
it { expect { subject }.not_to change { application.reload.secret } }
it { is_expected.to redirect_to(oauth_application_url(application)) }
it { is_expected.to have_gitlab_http_status(:unprocessable_entity) }
end
end

View File

@ -11,7 +11,8 @@ RSpec.describe 'Database schema', feature_category: :database do
IGNORED_INDEXES_ON_FKS = {
slack_integrations_scopes: %w[slack_api_scope_id],
p_ci_builds_metadata: %w[partition_id] # composable FK, the columns are reversed in the index definition
p_ci_builds_metadata: %w[partition_id], # composable FK, the columns are reversed in the index definition
p_ci_runner_machine_builds: %w[partition_id] # composable FK, the columns are reversed in the index definition
}.with_indifferent_access.freeze
TABLE_PARTITIONS = %w[ci_builds_metadata].freeze
@ -319,7 +320,7 @@ RSpec.describe 'Database schema', feature_category: :database do
# position. Using PARTITIONABLE_MODELS instead of iterating tables since when partitioning existing tables,
# the routing table only gets created after the PK has already been created, which would be too late for a check.
skip_tables = %w[Ci::RunnerMachineBuild]
skip_tables = %w[]
partitionable_models = Ci::Partitionable::Testing::PARTITIONABLE_MODELS
(partitionable_models - skip_tables).each do |klass|
model = klass.safe_constantize

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
FactoryBot.define do
factory :schema_inconsistency, class: '::Gitlab::Database::SchemaValidation::SchemaInconsistency' do
issue factory: :issue
object_name { 'name' }
table_name { 'table' }
valitador_name { 'validator' }
end
end

View File

@ -275,5 +275,42 @@ describe('Job table app', () => {
url: `${TEST_HOST}/?statuses=FAILED`,
});
});
it('resets query param after clearing tokens', () => {
createComponent();
jest.spyOn(urlUtils, 'updateHistory');
findFilteredSearch().vm.$emit('filterJobsBySearch', [mockFailedSearchToken]);
expect(successHandler).toHaveBeenCalledWith({
first: 30,
fullPath: 'gitlab-org/gitlab',
statuses: 'FAILED',
});
expect(countSuccessHandler).toHaveBeenCalledWith({
fullPath: 'gitlab-org/gitlab',
statuses: 'FAILED',
});
expect(urlUtils.updateHistory).toHaveBeenCalledWith({
url: `${TEST_HOST}/?statuses=FAILED`,
});
findFilteredSearch().vm.$emit('filterJobsBySearch', []);
expect(urlUtils.updateHistory).toHaveBeenCalledWith({
url: `${TEST_HOST}/`,
});
expect(successHandler).toHaveBeenCalledWith({
first: 30,
fullPath: 'gitlab-org/gitlab',
statuses: null,
});
expect(countSuccessHandler).toHaveBeenCalledWith({
fullPath: 'gitlab-org/gitlab',
statuses: null,
});
});
});
});

View File

@ -0,0 +1,116 @@
import { GlButton, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import OAuthSecret from '~/oauth_application/components/oauth_secret.vue';
import {
RENEW_SECRET_FAILURE,
RENEW_SECRET_SUCCESS,
WARNING_NO_SECRET,
} from '~/oauth_application/constants';
import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue';
jest.mock('~/alert');
const mockEvent = { preventDefault: jest.fn() };
describe('OAuthSecret', () => {
let wrapper;
const renewPath = '/applications/1/renew';
const createComponent = (provide = {}) => {
wrapper = shallowMount(OAuthSecret, {
provide: {
initialSecret: undefined,
renewPath,
...provide,
},
});
};
const findInputCopyToggleVisibility = () => wrapper.findComponent(InputCopyToggleVisibility);
const findRenewSecretButton = () => wrapper.findComponent(GlButton);
const findModal = () => wrapper.findComponent(GlModal);
describe('when secret is provided', () => {
const initialSecret = 'my secret';
beforeEach(() => {
createComponent({ initialSecret });
});
it('shows the masked secret', () => {
expect(findInputCopyToggleVisibility().props('value')).toBe(initialSecret);
});
it('shows the renew secret button', () => {
expect(findRenewSecretButton().exists()).toBe(true);
});
});
describe('when secret is not provided', () => {
beforeEach(() => {
createComponent();
});
it('shows an alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: WARNING_NO_SECRET,
variant: VARIANT_WARNING,
});
});
it('shows the renew secret button', () => {
expect(findRenewSecretButton().exists()).toBe(true);
});
describe('when renew secret button is selected', () => {
beforeEach(() => {
createComponent();
findRenewSecretButton().vm.$emit('click');
});
it('shows a modal', () => {
expect(findModal().props('visible')).toBe(true);
});
describe('when secret renewal succeeds', () => {
const initialSecret = 'my secret';
beforeEach(async () => {
const mockAxios = new MockAdapter(axios);
mockAxios.onPut().reply(HTTP_STATUS_OK, { secret: initialSecret });
findModal().vm.$emit('primary', mockEvent);
await waitForPromises();
});
it('shows an alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: RENEW_SECRET_SUCCESS,
variant: VARIANT_SUCCESS,
});
});
it('shows the new secret', () => {
expect(findInputCopyToggleVisibility().props('value')).toBe(initialSecret);
});
});
describe('when secret renewal fails', () => {
beforeEach(async () => {
const mockAxios = new MockAdapter(axios);
mockAxios.onPut().reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
findModal().vm.$emit('primary', mockEvent);
await waitForPromises();
});
it('creates an alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: RENEW_SECRET_FAILURE,
});
});
});
});
});
});

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::Catalog::ResourceType, feature_category: :pipeline_composition do
specify { expect(described_class.graphql_name).to eq('CiCatalogResource') }
it 'exposes the expected fields' do
expected_fields = %i[
id
name
description
icon
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end

View File

@ -297,22 +297,26 @@ RSpec.describe AvatarsHelper do
subject { helper.user_avatar_without_link(options) }
it 'displays user avatar' do
is_expected.to eq tag.img(alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, 16),
data: { container: 'body' },
class: 'avatar s16 has-tooltip',
title: user.name)
is_expected.to eq tag.img(
alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, 16),
data: { container: 'body' },
class: 'avatar s16 has-tooltip',
title: user.name
)
end
context 'with css_class parameter' do
let(:options) { { user: user, css_class: '.cat-pics' } }
it 'uses provided css_class' do
is_expected.to eq tag.img(alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, 16),
data: { container: 'body' },
class: "avatar s16 #{options[:css_class]} has-tooltip",
title: user.name)
is_expected.to eq tag.img(
alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, 16),
data: { container: 'body' },
class: "avatar s16 #{options[:css_class]} has-tooltip",
title: user.name
)
end
end
@ -320,11 +324,13 @@ RSpec.describe AvatarsHelper do
let(:options) { { user: user, size: 99 } }
it 'uses provided size' do
is_expected.to eq tag.img(alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, options[:size]),
data: { container: 'body' },
class: "avatar s#{options[:size]} has-tooltip",
title: user.name)
is_expected.to eq tag.img(
alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, options[:size]),
data: { container: 'body' },
class: "avatar s#{options[:size]} has-tooltip",
title: user.name
)
end
end
@ -332,11 +338,13 @@ RSpec.describe AvatarsHelper do
let(:options) { { user: user, url: '/over/the/rainbow.png' } }
it 'uses provided url' do
is_expected.to eq tag.img(alt: "#{user.name}'s avatar",
src: options[:url],
data: { container: 'body' },
class: "avatar s16 has-tooltip",
title: user.name)
is_expected.to eq tag.img(
alt: "#{user.name}'s avatar",
src: options[:url],
data: { container: 'body' },
class: "avatar s16 has-tooltip",
title: user.name
)
end
end
@ -344,11 +352,13 @@ RSpec.describe AvatarsHelper do
let(:options) { { user: user, lazy: true } }
it 'adds `lazy` class to class list, sets `data-src` with avatar URL and `src` with placeholder image' do
is_expected.to eq tag.img(alt: "#{user.name}'s avatar",
src: LazyImageTagHelper.placeholder_image,
data: { container: 'body', src: avatar_icon_for_user(user, 16) },
class: "avatar s16 has-tooltip lazy",
title: user.name)
is_expected.to eq tag.img(
alt: "#{user.name}'s avatar",
src: LazyImageTagHelper.placeholder_image,
data: { container: 'body', src: avatar_icon_for_user(user, 16) },
class: "avatar s16 has-tooltip lazy",
title: user.name
)
end
end
@ -357,11 +367,13 @@ RSpec.describe AvatarsHelper do
let(:options) { { user: user, has_tooltip: true } }
it 'adds has-tooltip' do
is_expected.to eq tag.img(alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, 16),
data: { container: 'body' },
class: "avatar s16 has-tooltip",
title: user.name)
is_expected.to eq tag.img(
alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, 16),
data: { container: 'body' },
class: "avatar s16 has-tooltip",
title: user.name
)
end
end
@ -369,10 +381,12 @@ RSpec.describe AvatarsHelper do
let(:options) { { user: user, has_tooltip: false } }
it 'does not add has-tooltip or data container' do
is_expected.to eq tag.img(alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, 16),
class: "avatar s16",
title: user.name)
is_expected.to eq tag.img(
alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, 16),
class: "avatar s16",
title: user.name
)
end
end
end
@ -384,20 +398,24 @@ RSpec.describe AvatarsHelper do
let(:options) { { user: user, user_name: 'Tinky Winky' } }
it 'prefers user parameter' do
is_expected.to eq tag.img(alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, 16),
data: { container: 'body' },
class: "avatar s16 has-tooltip",
title: user.name)
is_expected.to eq tag.img(
alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, 16),
data: { container: 'body' },
class: "avatar s16 has-tooltip",
title: user.name
)
end
end
it 'uses user_name and user_email parameter if user is not present' do
is_expected.to eq tag.img(alt: "#{options[:user_name]}'s avatar",
src: helper.avatar_icon_for_email(options[:user_email], 16),
data: { container: 'body' },
class: "avatar s16 has-tooltip",
title: options[:user_name])
is_expected.to eq tag.img(
alt: "#{options[:user_name]}'s avatar",
src: helper.avatar_icon_for_email(options[:user_email], 16),
data: { container: 'body' },
class: "avatar s16 has-tooltip",
title: options[:user_name]
)
end
end
@ -408,11 +426,13 @@ RSpec.describe AvatarsHelper do
let(:options) { { user: user_with_avatar, only_path: false } }
it 'will return avatar with a full path' do
is_expected.to eq tag.img(alt: "#{user_with_avatar.name}'s avatar",
src: avatar_icon_for_user(user_with_avatar, 16, only_path: false),
data: { container: 'body' },
class: "avatar s16 has-tooltip",
title: user_with_avatar.name)
is_expected.to eq tag.img(
alt: "#{user_with_avatar.name}'s avatar",
src: avatar_icon_for_user(user_with_avatar, 16, only_path: false),
data: { container: 'body' },
class: "avatar s16 has-tooltip",
title: user_with_avatar.name
)
end
end
@ -420,11 +440,13 @@ RSpec.describe AvatarsHelper do
let(:options) { { user_email: user_with_avatar.email, user_name: user_with_avatar.username, only_path: false } }
it 'will return avatar with a full path' do
is_expected.to eq tag.img(alt: "#{user_with_avatar.username}'s avatar",
src: helper.avatar_icon_for_email(user_with_avatar.email, 16, only_path: false),
data: { container: 'body' },
class: "avatar s16 has-tooltip",
title: user_with_avatar.username)
is_expected.to eq tag.img(
alt: "#{user_with_avatar.username}'s avatar",
src: helper.avatar_icon_for_email(user_with_avatar.email, 16, only_path: false),
data: { container: 'body' },
class: "avatar s16 has-tooltip",
title: user_with_avatar.username
)
end
end
end
@ -447,11 +469,13 @@ RSpec.describe AvatarsHelper do
let(:resource) { user.namespace }
it 'displays user avatar' do
is_expected.to eq tag.img(alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, 32),
data: { container: 'body' },
class: 'avatar s32 has-tooltip',
title: user.name)
is_expected.to eq tag.img(
alt: "#{user.name}'s avatar",
src: avatar_icon_for_user(user, 32),
data: { container: 'body' },
class: 'avatar s32 has-tooltip',
title: user.name
)
end
end

View File

@ -12,10 +12,12 @@ RSpec.describe EmojiHelper do
subject { helper.emoji_icon(emoji_text, options) }
it 'has no options' do
is_expected.to include('<gl-emoji',
"title=\"#{emoji_text}\"",
"data-name=\"#{emoji_text}\"",
"data-unicode-version=\"#{unicode_version}\"")
is_expected.to include(
'<gl-emoji',
"title=\"#{emoji_text}\"",
"data-name=\"#{emoji_text}\"",
"data-unicode-version=\"#{unicode_version}\""
)
is_expected.not_to include(aria_hidden_option)
end
@ -23,11 +25,13 @@ RSpec.describe EmojiHelper do
let(:options) { { 'aria-hidden': true } }
it 'applies aria-hidden' do
is_expected.to include('<gl-emoji',
"title=\"#{emoji_text}\"",
"data-name=\"#{emoji_text}\"",
"data-unicode-version=\"#{unicode_version}\"",
aria_hidden_option)
is_expected.to include(
'<gl-emoji',
"title=\"#{emoji_text}\"",
"data-name=\"#{emoji_text}\"",
"data-unicode-version=\"#{unicode_version}\"",
aria_hidden_option
)
end
end
end

View File

@ -33,12 +33,14 @@ RSpec.describe FeatureFlagsHelper do
subject { helper.edit_feature_flag_data }
it 'contains all the data needed to edit feature flags' do
is_expected.to include(endpoint: "/#{project.full_path}/-/feature_flags/#{feature_flag.iid}",
project_id: project.id,
feature_flags_path: "/#{project.full_path}/-/feature_flags",
environments_endpoint: "/#{project.full_path}/-/environments/search.json",
strategy_type_docs_page_path: "/help/operations/feature_flags#feature-flag-strategies",
environments_scope_docs_path: "/help/ci/environments/index.md#limit-the-environment-scope-of-a-cicd-variable")
is_expected.to include(
endpoint: "/#{project.full_path}/-/feature_flags/#{feature_flag.iid}",
project_id: project.id,
feature_flags_path: "/#{project.full_path}/-/feature_flags",
environments_endpoint: "/#{project.full_path}/-/environments/search.json",
strategy_type_docs_page_path: "/help/operations/feature_flags#feature-flag-strategies",
environments_scope_docs_path: "/help/ci/environments/index.md#limit-the-environment-scope-of-a-cicd-variable"
)
end
end
end

View File

@ -6,38 +6,35 @@ RSpec.describe NamespacesHelper do
let!(:admin) { create(:admin) }
let!(:admin_project_creation_level) { nil }
let!(:admin_group) do
create(:group,
:private,
project_creation_level: admin_project_creation_level)
create(:group, :private, project_creation_level: admin_project_creation_level)
end
let!(:user) { create(:user) }
let!(:user_project_creation_level) { nil }
let!(:user_group) do
create(:group,
:private,
project_creation_level: user_project_creation_level)
create(:group, :private, project_creation_level: user_project_creation_level)
end
let!(:subgroup1) do
create(:group,
:private,
parent: admin_group,
project_creation_level: nil)
create(:group, :private, parent: admin_group, project_creation_level: nil)
end
let!(:subgroup2) do
create(:group,
:private,
parent: admin_group,
project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS)
create(
:group,
:private,
parent: admin_group,
project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS
)
end
let!(:subgroup3) do
create(:group,
:private,
parent: admin_group,
project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
create(
:group,
:private,
parent: admin_group,
project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS
)
end
before do

View File

@ -64,10 +64,19 @@ RSpec.describe NotifyHelper do
mr_link_style = "font-weight: 600;color:#3777b0;text-decoration:none"
reviewer_avatar_style = "border-radius:12px;margin:-7px 0 -7px 3px;"
mr_link = link_to(merge_request.to_reference, merge_request_url(merge_request), style: mr_link_style).html_safe
reviewer_avatar = content_tag(:img, nil, height: "24", src: avatar_icon_for_user, style: reviewer_avatar_style, \
width: "24", alt: "Avatar", class: "avatar").html_safe
reviewer_link = link_to(reviewer.name, user_url(reviewer), style: "color:#333333;text-decoration:none;", \
class: "muted").html_safe
reviewer_avatar = content_tag(
:img,
nil,
height: "24",
src: avatar_icon_for_user,
style: reviewer_avatar_style,
width: "24",
alt: "Avatar",
class: "avatar"
).html_safe
reviewer_link = link_to(
reviewer.name, user_url(reviewer), style: "color:#333333;text-decoration:none;", class: "muted"
).html_safe
result = helper.merge_request_hash_param(merge_request, reviewer)
expect(result[:mr_highlight]).to eq '<span style="font-weight: 600;color:#333333;">'.html_safe
expect(result[:highlight_end]).to eq '</span>'.html_safe

View File

@ -128,12 +128,14 @@ RSpec.describe PageLayoutHelper do
describe 'a bare controller' do
it 'returns an empty context' do
expect(search_context).to have_attributes(project: nil,
group: nil,
snippets: [],
project_metadata: {},
group_metadata: {},
search_url: '/search')
expect(search_context).to have_attributes(
project: nil,
group: nil,
snippets: [],
project_metadata: {},
group_metadata: {},
search_url: '/search'
)
end
end
end

View File

@ -26,17 +26,19 @@ RSpec.describe ::Routing::PseudonymizationHelper do
context 'with controller for MR' do
let(:masked_url) { "http://localhost/namespace#{group.id}/project#{project.id}/-/merge_requests/#{merge_request.id}" }
let(:request) do
double(:Request,
path_parameters: {
controller: "projects/merge_requests",
action: "show",
namespace_id: group.name,
project_id: project.name,
id: merge_request.id.to_s
},
protocol: 'http',
host: 'localhost',
query_string: '')
double(
:Request,
path_parameters: {
controller: "projects/merge_requests",
action: "show",
namespace_id: group.name,
project_id: project.name,
id: merge_request.id.to_s
},
protocol: 'http',
host: 'localhost',
query_string: ''
)
end
before do
@ -49,17 +51,19 @@ RSpec.describe ::Routing::PseudonymizationHelper do
context 'with controller for issue' do
let(:masked_url) { "http://localhost/namespace#{group.id}/project#{project.id}/-/issues/#{issue.id}" }
let(:request) do
double(:Request,
path_parameters: {
controller: "projects/issues",
action: "show",
namespace_id: group.name,
project_id: project.name,
id: issue.id.to_s
},
protocol: 'http',
host: 'localhost',
query_string: '')
double(
:Request,
path_parameters: {
controller: "projects/issues",
action: "show",
namespace_id: group.name,
project_id: project.name,
id: issue.id.to_s
},
protocol: 'http',
host: 'localhost',
query_string: ''
)
end
before do
@ -74,16 +78,18 @@ RSpec.describe ::Routing::PseudonymizationHelper do
let(:group) { subgroup }
let(:project) { subproject }
let(:request) do
double(:Request,
path_parameters: {
controller: 'projects',
action: 'show',
namespace_id: subgroup.name,
id: subproject.name
},
protocol: 'http',
host: 'localhost',
query_string: '')
double(
:Request,
path_parameters: {
controller: 'projects',
action: 'show',
namespace_id: subgroup.name,
id: subproject.name
},
protocol: 'http',
host: 'localhost',
query_string: ''
)
end
before do
@ -97,15 +103,17 @@ RSpec.describe ::Routing::PseudonymizationHelper do
let(:masked_url) { "http://localhost/groups/namespace#{subgroup.id}/-/shared" }
let(:group) { subgroup }
let(:request) do
double(:Request,
path_parameters: {
controller: 'groups',
action: 'show',
id: subgroup.name
},
protocol: 'http',
host: 'localhost',
query_string: '')
double(
:Request,
path_parameters: {
controller: 'groups',
action: 'show',
id: subgroup.name
},
protocol: 'http',
host: 'localhost',
query_string: ''
)
end
before do
@ -118,17 +126,19 @@ RSpec.describe ::Routing::PseudonymizationHelper do
context 'with controller for blob with file path' do
let(:masked_url) { "http://localhost/namespace#{group.id}/project#{project.id}/-/blob/:repository_path" }
let(:request) do
double(:Request,
path_parameters: {
controller: 'projects/blob',
action: 'show',
namespace_id: group.name,
project_id: project.name,
id: 'master/README.md'
},
protocol: 'http',
host: 'localhost',
query_string: '')
double(
:Request,
path_parameters: {
controller: 'projects/blob',
action: 'show',
namespace_id: group.name,
project_id: project.name,
id: 'master/README.md'
},
protocol: 'http',
host: 'localhost',
query_string: ''
)
end
before do
@ -141,14 +151,16 @@ RSpec.describe ::Routing::PseudonymizationHelper do
context 'when assignee_username is present' do
let(:masked_url) { "http://localhost/dashboard/issues?assignee_username=masked_assignee_username" }
let(:request) do
double(:Request,
path_parameters: {
controller: 'dashboard',
action: 'issues'
},
protocol: 'http',
host: 'localhost',
query_string: 'assignee_username=root')
double(
:Request,
path_parameters: {
controller: 'dashboard',
action: 'issues'
},
protocol: 'http',
host: 'localhost',
query_string: 'assignee_username=root'
)
end
before do
@ -161,14 +173,16 @@ RSpec.describe ::Routing::PseudonymizationHelper do
context 'when author_username is present' do
let(:masked_url) { "http://localhost/dashboard/issues?author_username=masked_author_username&scope=all&state=opened" }
let(:request) do
double(:Request,
path_parameters: {
controller: 'dashboard',
action: 'issues'
},
protocol: 'http',
host: 'localhost',
query_string: 'author_username=root&scope=all&state=opened')
double(
:Request,
path_parameters: {
controller: 'dashboard',
action: 'issues'
},
protocol: 'http',
host: 'localhost',
query_string: 'author_username=root&scope=all&state=opened'
)
end
before do
@ -181,14 +195,16 @@ RSpec.describe ::Routing::PseudonymizationHelper do
context 'when some query params are not required to be masked' do
let(:masked_url) { "http://localhost/dashboard/issues?author_username=masked_author_username&scope=all&state=masked_state&tab=2" }
let(:request) do
double(:Request,
path_parameters: {
controller: 'dashboard',
action: 'issues'
},
protocol: 'http',
host: 'localhost',
query_string: 'author_username=root&scope=all&state=opened&tab=2')
double(
:Request,
path_parameters: {
controller: 'dashboard',
action: 'issues'
},
protocol: 'http',
host: 'localhost',
query_string: 'author_username=root&scope=all&state=opened&tab=2'
)
end
before do
@ -202,14 +218,16 @@ RSpec.describe ::Routing::PseudonymizationHelper do
context 'when query string has keys with the same names as path params' do
let(:masked_url) { "http://localhost/dashboard/issues?action=masked_action&scope=all&state=opened" }
let(:request) do
double(:Request,
path_parameters: {
controller: 'dashboard',
action: 'issues'
},
protocol: 'http',
host: 'localhost',
query_string: 'action=foobar&scope=all&state=opened')
double(
:Request,
path_parameters: {
controller: 'dashboard',
action: 'issues'
},
protocol: 'http',
host: 'localhost',
query_string: 'action=foobar&scope=all&state=opened'
)
end
before do
@ -223,16 +241,18 @@ RSpec.describe ::Routing::PseudonymizationHelper do
describe 'when url has no params to mask' do
let(:original_url) { 'http://localhost/-/security/vulnerabilities' }
let(:request) do
double(:Request,
path_parameters: {
controller: 'security/vulnerabilities',
action: 'index'
},
protocol: 'http',
host: 'localhost',
query_string: '',
original_fullpath: '/-/security/vulnerabilities',
original_url: original_url)
double(
:Request,
path_parameters: {
controller: 'security/vulnerabilities',
action: 'index'
},
protocol: 'http',
host: 'localhost',
query_string: '',
original_fullpath: '/-/security/vulnerabilities',
original_url: original_url
)
end
before do
@ -247,15 +267,17 @@ RSpec.describe ::Routing::PseudonymizationHelper do
describe 'when it raises exception' do
context 'calls error tracking' do
let(:request) do
double(:Request,
path_parameters: {
controller: 'dashboard',
action: 'issues'
},
protocol: 'http',
host: 'localhost',
query_string: 'assignee_username=root',
original_fullpath: '/dashboard/issues?assignee_username=root')
double(
:Request,
path_parameters: {
controller: 'dashboard',
action: 'issues'
},
protocol: 'http',
host: 'localhost',
query_string: 'assignee_username=root',
original_fullpath: '/dashboard/issues?assignee_username=root'
)
end
before do

View File

@ -24,18 +24,22 @@ RSpec.describe StorageHelper do
describe "#storage_counters_details" do
let_it_be(:namespace) { create(:namespace) }
let_it_be(:project) do
create(:project,
namespace: namespace,
statistics: build(:project_statistics,
namespace: namespace,
repository_size: 10.kilobytes,
wiki_size: 10.bytes,
lfs_objects_size: 20.gigabytes,
build_artifacts_size: 30.megabytes,
pipeline_artifacts_size: 11.megabytes,
snippets_size: 40.megabytes,
packages_size: 12.megabytes,
uploads_size: 15.megabytes))
create(
:project,
namespace: namespace,
statistics: build(
:project_statistics,
namespace: namespace,
repository_size: 10.kilobytes,
wiki_size: 10.bytes,
lfs_objects_size: 20.gigabytes,
build_artifacts_size: 30.megabytes,
pipeline_artifacts_size: 11.megabytes,
snippets_size: 40.megabytes,
packages_size: 12.megabytes,
uploads_size: 15.megabytes
)
)
end
let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / Pipeline Artifacts: 11 MB / LFS: 20 GB / Snippets: 40 MB / Packages: 12 MB / Uploads: 15 MB' }

View File

@ -9,20 +9,21 @@ RSpec.describe TodosHelper do
let_it_be(:issue) { create(:issue, title: 'Issue 1', project: project) }
let_it_be(:design) { create(:design, issue: issue) }
let_it_be(:note) do
create(:note,
project: issue.project,
note: 'I am note, hear me roar')
create(:note, project: issue.project, note: 'I am note, hear me roar')
end
let_it_be(:group) { create(:group, :public, name: 'Group 1') }
let_it_be(:design_todo) do
create(:todo, :mentioned,
user: user,
project: project,
target: design,
author: author,
note: note)
create(
:todo,
:mentioned,
user: user,
project: project,
target: design,
author: author,
note: note
)
end
let_it_be(:alert_todo) do
@ -93,11 +94,14 @@ RSpec.describe TodosHelper do
context 'when given a non-design todo' do
let(:todo) do
build_stubbed(:todo, :assigned,
user: user,
project: issue.project,
target: issue,
author: author)
build_stubbed(
:todo,
:assigned,
user: user,
project: issue.project,
target: issue,
author: author
)
end
it 'returns the title' do
@ -154,11 +158,13 @@ RSpec.describe TodosHelper do
context 'when a user requests access to group' do
let_it_be(:group_access_request_todo) do
create(:todo,
target_id: group.id,
target_type: group.class.polymorphic_name,
group: group,
action: Todo::MEMBER_ACCESS_REQUESTED)
create(
:todo,
target_id: group.id,
target_type: group.class.polymorphic_name,
group: group,
action: Todo::MEMBER_ACCESS_REQUESTED
)
end
it 'responds with access requests tab' do

View File

@ -70,10 +70,12 @@ RSpec.describe Users::GroupCalloutsHelper do
context 'when the invite_members_banner has been dismissed' do
before do
create(:group_callout,
user: user,
group: group,
feature_name: described_class::INVITE_MEMBERS_BANNER)
create(
:group_callout,
user: user,
group: group,
feature_name: described_class::INVITE_MEMBERS_BANNER
)
end
it { is_expected.to eq(false) }

View File

@ -178,8 +178,10 @@ RSpec.describe VisibilityLevelHelper, feature_category: :system_access do
end
before do
stub_application_setting(restricted_visibility_levels: restricted_levels,
default_project_visibility: global_default_level)
stub_application_setting(
restricted_visibility_levels: restricted_levels,
default_project_visibility: global_default_level
)
end
with_them do

View File

@ -11,8 +11,8 @@ RSpec.describe Gitlab::Database::SchemaValidation::Inconsistency, feature_catego
let(:structure_stmt) { PgQuery.parse(structure_sql_statement).tree.stmts.first.stmt.index_stmt }
let(:database_stmt) { PgQuery.parse(database_statement).tree.stmts.first.stmt.index_stmt }
let(:structure_sql_object) { Gitlab::Database::SchemaValidation::Index.new(structure_stmt) }
let(:database_object) { Gitlab::Database::SchemaValidation::Index.new(database_stmt) }
let(:structure_sql_object) { Gitlab::Database::SchemaValidation::SchemaObjects::Index.new(structure_stmt) }
let(:database_object) { Gitlab::Database::SchemaValidation::SchemaObjects::Index.new(database_stmt) }
subject(:inconsistency) { described_class.new(validator, structure_sql_object, database_object) }
@ -44,6 +44,12 @@ RSpec.describe Gitlab::Database::SchemaValidation::Inconsistency, feature_catego
end
end
describe '#table_name' do
it 'returns the table name' do
expect(inconsistency.table_name).to eq('achievements')
end
end
describe '#inspect' do
let(:expected_output) do
<<~MSG

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::SchemaValidation::SchemaInconsistency, type: :model, feature_category: :database do
it { is_expected.to be_a ApplicationRecord }
describe 'associations' do
it { is_expected.to belong_to(:issue) }
end
describe "Validations" do
it { is_expected.to validate_presence_of(:object_name) }
it { is_expected.to validate_presence_of(:valitador_name) }
it { is_expected.to validate_presence_of(:table_name) }
end
end

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::SchemaValidation::SchemaObjects::Index, feature_category: :database do
let(:statement) { 'CREATE INDEX index_name ON public.achievements USING btree (namespace_id)' }
let(:name) { 'index_name' }
let(:table_name) { 'achievements' }
include_examples 'schema objects assertions for', 'index_stmt'
end

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::SchemaValidation::SchemaObjects::Trigger, feature_category: :database do
let(:statement) { 'CREATE TRIGGER my_trigger BEFORE INSERT ON todos FOR EACH ROW EXECUTE FUNCTION trigger()' }
let(:name) { 'my_trigger' }
let(:table_name) { 'todos' }
include_examples 'schema objects assertions for', 'create_trig_stmt'
end

View File

@ -0,0 +1,82 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::SchemaValidation::TrackInconsistency, feature_category: :database do
describe '#execute' do
let(:validator) { Gitlab::Database::SchemaValidation::Validators::DifferentDefinitionIndexes }
let(:database_statement) { 'CREATE INDEX index_name ON public.achievements USING btree (namespace_id)' }
let(:structure_sql_statement) { 'CREATE INDEX index_name ON public.achievements USING btree (id)' }
let(:structure_stmt) { PgQuery.parse(structure_sql_statement).tree.stmts.first.stmt.index_stmt }
let(:database_stmt) { PgQuery.parse(database_statement).tree.stmts.first.stmt.index_stmt }
let(:structure_sql_object) { Gitlab::Database::SchemaValidation::SchemaObjects::Index.new(structure_stmt) }
let(:database_object) { Gitlab::Database::SchemaValidation::SchemaObjects::Index.new(database_stmt) }
let(:inconsistency) do
Gitlab::Database::SchemaValidation::Inconsistency.new(validator, structure_sql_object, database_object)
end
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
subject(:execute) { described_class.new(inconsistency, project, user).execute }
before do
stub_spam_services
end
context 'when is not GitLab.com' do
it 'does not create a schema inconsistency record' do
allow(Gitlab).to receive(:com?).and_return(false)
expect { execute }.not_to change { Gitlab::Database::SchemaValidation::SchemaInconsistency.count }
end
end
context 'when the issue creation fails' do
let(:issue_creation) { instance_double(Mutations::Issues::Create, resolve: { errors: 'error' }) }
before do
allow(Mutations::Issues::Create).to receive(:new).and_return(issue_creation)
end
it 'does not create a schema inconsistency record' do
allow(Gitlab).to receive(:com?).and_return(true)
expect { execute }.not_to change { Gitlab::Database::SchemaValidation::SchemaInconsistency.count }
end
end
context 'when a new inconsistency is found' do
before do
project.add_developer(user)
end
it 'creates a new schema inconsistency record' do
allow(Gitlab).to receive(:com?).and_return(true)
expect { execute }.to change { Gitlab::Database::SchemaValidation::SchemaInconsistency.count }
end
end
context 'when the schema inconsistency already exists' do
before do
project.add_developer(user)
end
let!(:schema_inconsistency) do
create(:schema_inconsistency, object_name: 'index_name', table_name: 'achievements',
valitador_name: 'different_definition_indexes')
end
it 'does not create a schema inconsistency record' do
allow(Gitlab).to receive(:com?).and_return(true)
expect { execute }.not_to change { Gitlab::Database::SchemaValidation::SchemaInconsistency.count }
end
end
end
end

View File

@ -3,6 +3,12 @@
require 'spec_helper'
RSpec.describe Ci::Catalog::Resource, feature_category: :pipeline_composition do
it { is_expected.to belong_to(:project) }
it { is_expected.to delegate_method(:avatar_path).to(:project) }
it { is_expected.to delegate_method(:description).to(:project) }
it { is_expected.to delegate_method(:name).to(:project) }
describe '.for_projects' do
it 'returns catalog resources for the given project IDs' do
project = create(:project)

View File

@ -44,12 +44,14 @@ module APIInternalBaseHelpers
end
def push(key, container, protocol = 'ssh', env: nil, changes: nil)
push_with_path(key,
full_path: full_path_for(container),
gl_repository: gl_repository_for(container),
protocol: protocol,
env: env,
changes: changes)
push_with_path(
key,
full_path: full_path_for(container),
gl_repository: gl_repository_for(container),
protocol: protocol,
env: env,
changes: changes
)
end
def push_with_path(key, full_path:, gl_repository: nil, protocol: 'ssh', env: nil, changes: nil)

View File

@ -29,13 +29,15 @@ module BoardHelpers
# ensure there is enough horizontal space for four board lists
resize_window(2000, 800)
drag_to(selector: selector,
scrollable: '#board-app',
list_from_index: list_from_index,
from_index: from_index,
to_index: to_index,
list_to_index: list_to_index,
perform_drop: perform_drop)
drag_to(
selector: selector,
scrollable: '#board-app',
list_from_index: list_from_index,
from_index: from_index,
to_index: to_index,
list_to_index: list_to_index,
perform_drop: perform_drop
)
end
wait_for_requests

View File

@ -3,11 +3,13 @@
module Ci
module SourcePipelineHelpers
def create_source_pipeline(upstream, downstream)
create(:ci_sources_pipeline,
source_job: create(:ci_build, pipeline: upstream),
source_project: upstream.project,
pipeline: downstream,
project: downstream.project)
create(
:ci_sources_pipeline,
source_job: create(:ci_build, pipeline: upstream),
source_project: upstream.project,
pipeline: downstream,
project: downstream.project
)
end
end
end

View File

@ -2,22 +2,32 @@
module FeatureFlagHelpers
def create_flag(project, name, active = true, description: nil, version: Operations::FeatureFlag.versions['new_version_flag'])
create(:operations_feature_flag, name: name, active: active, version: version,
description: description, project: project)
create(
:operations_feature_flag,
name: name,
active: active,
version: version,
description: description,
project: project
)
end
def create_scope(feature_flag, environment_scope, active = true, strategies = [{ name: "default", parameters: {} }])
create(:operations_feature_flag_scope,
create(
:operations_feature_flag_scope,
feature_flag: feature_flag,
environment_scope: environment_scope,
active: active,
strategies: strategies)
strategies: strategies
)
end
def create_strategy(feature_flag, name = 'default', parameters = {})
create(:operations_strategy,
create(
:operations_strategy,
feature_flag: feature_flag,
name: name)
name: name
)
end
def within_feature_flag_row(index)

View File

@ -89,13 +89,16 @@ module GraphqlHelpers
# All mutations accept a single `:input` argument. Wrap arguments here.
args = { input: args } if resolver_class <= ::Mutations::BaseMutation && !args.key?(:input)
resolve_field(field, obj,
args: args,
ctx: ctx,
schema: schema,
object_type: resolver_parent,
extras: { parent: parent, lookahead: lookahead },
arg_style: arg_style)
resolve_field(
field,
obj,
args: args,
ctx: ctx,
schema: schema,
object_type: resolver_parent,
extras: { parent: parent, lookahead: lookahead },
arg_style: arg_style
)
end
# Resolve the value of a field on an object.
@ -513,20 +516,23 @@ module GraphqlHelpers
end
def post_graphql_mutation(mutation, current_user: nil, token: {})
post_graphql(mutation.query,
current_user: current_user,
variables: mutation.variables,
token: token)
post_graphql(
mutation.query,
current_user: current_user,
variables: mutation.variables,
token: token
)
end
def post_graphql_mutation_with_uploads(mutation, current_user: nil)
file_paths = file_paths_in_mutation(mutation)
params = mutation_to_apollo_uploads_param(mutation, files: file_paths)
workhorse_post_with_file(api('/', current_user, version: 'graphql'),
params: params,
file_key: '1'
)
workhorse_post_with_file(
api('/', current_user, version: 'graphql'),
params: params,
file_key: '1'
)
end
def file_paths_in_mutation(mutation)

View File

@ -2,9 +2,11 @@
module StubObjectStorage
def stub_dependency_proxy_object_storage(**params)
stub_object_storage_uploader(config: ::Gitlab.config.dependency_proxy.object_store,
uploader: ::DependencyProxy::FileUploader,
**params)
stub_object_storage_uploader(
config: ::Gitlab.config.dependency_proxy.object_store,
uploader: ::DependencyProxy::FileUploader,
**params
)
end
def stub_object_storage_uploader(
@ -36,8 +38,10 @@ module StubObjectStorage
return unless enabled
stub_object_storage(connection_params: uploader.object_store_credentials,
remote_directory: old_config.remote_directory)
stub_object_storage(
connection_params: uploader.object_store_credentials,
remote_directory: old_config.remote_directory
)
end
def stub_object_storage(connection_params:, remote_directory:)
@ -55,75 +59,99 @@ module StubObjectStorage
end
def stub_artifacts_object_storage(uploader = JobArtifactUploader, **params)
stub_object_storage_uploader(config: Gitlab.config.artifacts.object_store,
uploader: uploader,
**params)
stub_object_storage_uploader(
config: Gitlab.config.artifacts.object_store,
uploader: uploader,
**params
)
end
def stub_external_diffs_object_storage(uploader = described_class, **params)
stub_object_storage_uploader(config: Gitlab.config.external_diffs.object_store,
uploader: uploader,
**params)
stub_object_storage_uploader(
config: Gitlab.config.external_diffs.object_store,
uploader: uploader,
**params
)
end
def stub_lfs_object_storage(**params)
stub_object_storage_uploader(config: Gitlab.config.lfs.object_store,
uploader: LfsObjectUploader,
**params)
stub_object_storage_uploader(
config: Gitlab.config.lfs.object_store,
uploader: LfsObjectUploader,
**params
)
end
def stub_package_file_object_storage(**params)
stub_object_storage_uploader(config: Gitlab.config.packages.object_store,
uploader: ::Packages::PackageFileUploader,
**params)
stub_object_storage_uploader(
config: Gitlab.config.packages.object_store,
uploader: ::Packages::PackageFileUploader,
**params
)
end
def stub_rpm_repository_file_object_storage(**params)
stub_object_storage_uploader(config: Gitlab.config.packages.object_store,
uploader: ::Packages::Rpm::RepositoryFileUploader,
**params)
stub_object_storage_uploader(
config: Gitlab.config.packages.object_store,
uploader: ::Packages::Rpm::RepositoryFileUploader,
**params
)
end
def stub_composer_cache_object_storage(**params)
stub_object_storage_uploader(config: Gitlab.config.packages.object_store,
uploader: ::Packages::Composer::CacheUploader,
**params)
stub_object_storage_uploader(
config: Gitlab.config.packages.object_store,
uploader: ::Packages::Composer::CacheUploader,
**params
)
end
def debian_component_file_object_storage(**params)
stub_object_storage_uploader(config: Gitlab.config.packages.object_store,
uploader: ::Packages::Debian::ComponentFileUploader,
**params)
stub_object_storage_uploader(
config: Gitlab.config.packages.object_store,
uploader: ::Packages::Debian::ComponentFileUploader,
**params
)
end
def debian_distribution_release_file_object_storage(**params)
stub_object_storage_uploader(config: Gitlab.config.packages.object_store,
uploader: ::Packages::Debian::DistributionReleaseFileUploader,
**params)
stub_object_storage_uploader(
config: Gitlab.config.packages.object_store,
uploader: ::Packages::Debian::DistributionReleaseFileUploader,
**params
)
end
def stub_uploads_object_storage(uploader = described_class, **params)
stub_object_storage_uploader(config: Gitlab.config.uploads.object_store,
uploader: uploader,
**params)
stub_object_storage_uploader(
config: Gitlab.config.uploads.object_store,
uploader: uploader,
**params
)
end
def stub_ci_secure_file_object_storage(**params)
stub_object_storage_uploader(config: Gitlab.config.ci_secure_files.object_store,
uploader: Ci::SecureFileUploader,
**params)
stub_object_storage_uploader(
config: Gitlab.config.ci_secure_files.object_store,
uploader: Ci::SecureFileUploader,
**params
)
end
def stub_terraform_state_object_storage(**params)
stub_object_storage_uploader(config: Gitlab.config.terraform_state.object_store,
uploader: Terraform::StateUploader,
**params)
stub_object_storage_uploader(
config: Gitlab.config.terraform_state.object_store,
uploader: Terraform::StateUploader,
**params
)
end
def stub_pages_object_storage(uploader = described_class, **params)
stub_object_storage_uploader(config: Gitlab.config.pages.object_store,
uploader: uploader,
**params)
stub_object_storage_uploader(
config: Gitlab.config.pages.object_store,
uploader: uploader,
**params
)
end
def stub_object_storage_multipart_init(endpoint, upload_id = "upload_id")

View File

@ -29,31 +29,48 @@ module WorkhorseHelpers
# workhorse_form_with_file will transform file_key inside params as if it was disk accelerated by workhorse
def workhorse_form_with_file(url, file_key:, params:, method: :post)
workhorse_request_with_file(method, url,
file_key: file_key,
params: params,
env: { 'CONTENT_TYPE' => 'multipart/form-data' },
send_rewritten_field: true
workhorse_request_with_file(
method, url,
file_key: file_key,
params: params,
env: { 'CONTENT_TYPE' => 'multipart/form-data' },
send_rewritten_field: true
)
end
# workhorse_finalize will transform file_key inside params as if it was the finalize call of an inline object storage upload.
# note that based on the content of the params it can simulate a disc acceleration or an object storage upload
def workhorse_finalize(url, file_key:, params:, method: :post, headers: {}, send_rewritten_field: false)
workhorse_finalize_with_multiple_files(url, method: method, file_keys: file_key, params: params, headers: headers, send_rewritten_field: send_rewritten_field)
workhorse_finalize_with_multiple_files(
url,
method: method,
file_keys: file_key,
params: params,
headers: headers,
send_rewritten_field: send_rewritten_field
)
end
def workhorse_finalize_with_multiple_files(url, file_keys:, params:, method: :post, headers: {}, send_rewritten_field: false)
workhorse_request_with_multiple_files(method, url,
file_keys: file_keys,
params: params,
extra_headers: headers,
send_rewritten_field: send_rewritten_field
workhorse_request_with_multiple_files(
method, url,
file_keys: file_keys,
params: params,
extra_headers: headers,
send_rewritten_field: send_rewritten_field
)
end
def workhorse_request_with_file(method, url, file_key:, params:, send_rewritten_field:, env: {}, extra_headers: {})
workhorse_request_with_multiple_files(method, url, file_keys: file_key, params: params, env: env, extra_headers: extra_headers, send_rewritten_field: send_rewritten_field)
workhorse_request_with_multiple_files(
method,
url,
file_keys: file_key,
params: params,
env: env,
extra_headers: extra_headers,
send_rewritten_field: send_rewritten_field
)
end
def workhorse_request_with_multiple_files(method, url, file_keys:, params:, send_rewritten_field:, env: {}, extra_headers: {})

View File

@ -1868,7 +1868,6 @@
- './ee/spec/models/merge_requests/external_status_check_spec.rb'
- './ee/spec/models/merge_request_spec.rb'
- './ee/spec/models/merge_requests/status_check_response_spec.rb'
- './ee/spec/models/merge_train_spec.rb'
- './ee/spec/models/milestone_release_spec.rb'
- './ee/spec/models/milestone_spec.rb'
- './ee/spec/models/namespace_limit_spec.rb'

View File

@ -19,7 +19,7 @@ RSpec.shared_examples 'manage applications' do
expect(page).to have_content _('This is the only time the secret is accessible. Copy the secret and store it securely')
expect(page).to have_link('Continue', href: index_path)
expect(page).to have_css("button[title=\"Copy secret\"]", text: 'Copy')
expect(page).to have_button(_('Copy secret'))
click_on 'Edit'

View File

@ -17,4 +17,10 @@ RSpec.shared_examples "schema objects assertions for" do |stmt_name|
expect(schema_object.statement).to eq(statement)
end
end
describe '#table_name' do
it 'returns schema object table_name' do
expect(schema_object.table_name).to eq(table_name)
end
end
end