Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c5d8b7e690
commit
eb4b72630a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
d7ad67347247776ec267d4f2056e2c4cffcf4ebd
|
||||
c31b9fed97bb01a1790496386ceab8e31e76b1d8
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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.',
|
||||
);
|
||||
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import initApplicationDeleteButtons from '~/admin/applications';
|
||||
import { initOAuthApplicationSecret } from '~/oauth_application';
|
||||
|
||||
initApplicationDeleteButtons();
|
||||
initOAuthApplicationSecret();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
import { initOAuthApplicationSecret } from '~/oauth_application';
|
||||
|
||||
initOAuthApplicationSecret();
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { initOAuthApplicationSecret } from '~/oauth_application';
|
||||
|
||||
initOAuthApplicationSecret();
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
4ca98e9c93245a8fc1f4124d00d47d73d12b961affde1d53b7262ffc93582d83
|
||||
|
|
@ -0,0 +1 @@
|
|||
a85e3139d843295e666867129575818f61983a8b16eaa73f9b470e394d9c5476
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -193,13 +193,18 @@ After you have the route mapping set up, it takes effect in the following locati
|
|||
|
||||

|
||||
|
||||
## 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 -->
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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: {})
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue