Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8132e39e1b
commit
15ea3fec22
|
@ -77,6 +77,7 @@ eslint-report.html
|
|||
/.gitlab_kas_secret
|
||||
/webpack-report/
|
||||
/crystalball/
|
||||
/deprecations/
|
||||
/knapsack/
|
||||
/rspec_flaky/
|
||||
/locale/**/LC_MESSAGES
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
RUBY_GC_MALLOC_LIMIT: 67108864
|
||||
RUBY_GC_MALLOC_LIMIT_MAX: 134217728
|
||||
CRYSTALBALL: "true"
|
||||
RECORD_DEPRECATIONS: "true"
|
||||
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
|
||||
script:
|
||||
- *base-script
|
||||
|
@ -31,6 +32,7 @@
|
|||
paths:
|
||||
- coverage/
|
||||
- crystalball/
|
||||
- deprecations/
|
||||
- knapsack/
|
||||
- rspec_flaky/
|
||||
- rspec_profiling/
|
||||
|
|
|
@ -36,8 +36,7 @@ If applicable, any groups/projects that are happy to have this feature turned on
|
|||
- [ ] Ensure that documentation has been updated
|
||||
- [ ] Enable on GitLab.com for individual groups/projects listed above and verify behaviour (`/chatops run feature set --project=gitlab-org/gitlab feature_name true`)
|
||||
- [ ] Coordinate a time to enable the flag with the SRE oncall and release managers
|
||||
- In `#production` by pinging `@sre-oncall`
|
||||
- In `#g_delivery` by pinging `@release-managers`
|
||||
- In `#production` mention `@sre-oncall` and `@release-managers`. Once an SRE on call and Release Manager on call confirm, you can proceed with the rollout
|
||||
- [ ] Announce on the issue an estimated time this will be enabled on GitLab.com
|
||||
- [ ] Enable on GitLab.com by running chatops command in `#production` (`/chatops run feature set feature_name true`)
|
||||
- [ ] Cross post chatops Slack command to `#support_gitlab-com` ([more guidance when this is necessary in the dev docs](https://docs.gitlab.com/ee/development/feature_flags/controls.html#where-to-run-commands)) and in your team channel
|
||||
|
|
|
@ -47,6 +47,10 @@ Cop/StaticTranslationDefinition:
|
|||
- 'spec/**/*'
|
||||
- 'ee/spec/**/*'
|
||||
|
||||
Lint/LastKeywordArgument:
|
||||
Enabled: true
|
||||
Safe: false
|
||||
|
||||
# This cop checks whether some constant value isn't a
|
||||
# mutable literal (e.g. array or hash).
|
||||
Style/MutableConstant:
|
||||
|
|
1
Gemfile
1
Gemfile
|
@ -351,6 +351,7 @@ group :development do
|
|||
end
|
||||
|
||||
group :development, :test do
|
||||
gem 'deprecation_toolkit', '~> 1.5.1', require: false
|
||||
gem 'bullet', '~> 6.1.0'
|
||||
gem 'pry-byebug', '~> 3.9.0', platform: :mri
|
||||
gem 'pry-rails', '~> 0.3.9'
|
||||
|
|
|
@ -224,6 +224,8 @@ GEM
|
|||
declarative-option (0.1.0)
|
||||
default_value_for (3.3.0)
|
||||
activerecord (>= 3.2.0, < 6.1)
|
||||
deprecation_toolkit (1.5.1)
|
||||
activesupport (>= 4.2)
|
||||
derailed_benchmarks (1.7.0)
|
||||
benchmark-ips (~> 2)
|
||||
get_process_mem (~> 0)
|
||||
|
@ -1302,6 +1304,7 @@ DEPENDENCIES
|
|||
database_cleaner (~> 1.7.0)
|
||||
deckar01-task_list (= 2.3.1)
|
||||
default_value_for (~> 3.3.0)
|
||||
deprecation_toolkit (~> 1.5.1)
|
||||
derailed_benchmarks
|
||||
device_detector
|
||||
devise (~> 4.7.2)
|
||||
|
|
|
@ -216,8 +216,12 @@ export default {
|
|||
return {
|
||||
name: this.currentIntegration?.name || '',
|
||||
active: this.currentIntegration?.active || false,
|
||||
token: this.currentIntegration?.token || this.selectedIntegrationType.token,
|
||||
url: this.currentIntegration?.url || this.selectedIntegrationType.url,
|
||||
token:
|
||||
this.currentIntegration?.token ||
|
||||
(this.selectedIntegrationType !== this.generic ? this.selectedIntegrationType.token : ''),
|
||||
url:
|
||||
this.currentIntegration?.url ||
|
||||
(this.selectedIntegrationType !== this.generic ? this.selectedIntegrationType.url : ''),
|
||||
apiUrl: this.currentIntegration?.apiUrl || '',
|
||||
};
|
||||
},
|
||||
|
@ -246,8 +250,20 @@ export default {
|
|||
canEditPayload() {
|
||||
return this.hasSamplePayload && !this.resetSamplePayloadConfirmed;
|
||||
},
|
||||
isResetAuthKeyDisabled() {
|
||||
return !this.active && !this.integrationForm.token !== '';
|
||||
},
|
||||
isPayloadEditDisabled() {
|
||||
return !this.active || this.canEditPayload;
|
||||
return this.glFeatures.multipleHttpIntegrationsCustomMapping
|
||||
? !this.active || this.canEditPayload
|
||||
: !this.active;
|
||||
},
|
||||
isSubmitTestPayloadDisabled() {
|
||||
return (
|
||||
!this.active ||
|
||||
Boolean(this.integrationTestPayload.error) ||
|
||||
this.integrationTestPayload.json === ''
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
@ -257,7 +273,7 @@ export default {
|
|||
}
|
||||
this.selectedIntegration = val.type;
|
||||
this.active = val.active;
|
||||
if (val.type === typeSet.http) this.getIntegrationMapping(val.id);
|
||||
if (val.type === typeSet.http && this.showMappingBuilder) this.getIntegrationMapping(val.id);
|
||||
return this.integrationTypeSelect();
|
||||
},
|
||||
},
|
||||
|
@ -297,14 +313,8 @@ export default {
|
|||
});
|
||||
},
|
||||
submitWithTestPayload() {
|
||||
return service
|
||||
.updateTestAlert(this.testAlertPayload)
|
||||
.then(() => {
|
||||
this.submit();
|
||||
})
|
||||
.catch(() => {
|
||||
this.$emit('test-payload-failure');
|
||||
});
|
||||
this.$emit('set-test-alert-payload', this.testAlertPayload);
|
||||
this.submit();
|
||||
},
|
||||
submit() {
|
||||
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
|
||||
|
@ -323,6 +333,7 @@ export default {
|
|||
return this.$emit('update-integration', integrationPayload);
|
||||
}
|
||||
|
||||
this.reset();
|
||||
return this.$emit('create-new-integration', integrationPayload);
|
||||
},
|
||||
reset() {
|
||||
|
@ -539,7 +550,7 @@ export default {
|
|||
</template>
|
||||
</gl-form-input-group>
|
||||
|
||||
<gl-button v-gl-modal.authKeyModal :disabled="!active">
|
||||
<gl-button v-gl-modal.authKeyModal :disabled="isResetAuthKeyDisabled">
|
||||
{{ $options.i18n.integrationFormSteps.step3.reset }}
|
||||
</gl-button>
|
||||
<gl-modal
|
||||
|
@ -642,7 +653,7 @@ export default {
|
|||
<gl-button
|
||||
v-if="!isManagingOpsgenie"
|
||||
data-testid="integration-test-and-submit"
|
||||
:disabled="Boolean(integrationTestPayload.error)"
|
||||
:disabled="isSubmitTestPayloadDisabled"
|
||||
category="secondary"
|
||||
variant="success"
|
||||
class="gl-mx-3 js-no-auto-disable"
|
|
@ -14,7 +14,8 @@ import resetHttpTokenMutation from '../graphql/mutations/reset_http_token.mutati
|
|||
import resetPrometheusTokenMutation from '../graphql/mutations/reset_prometheus_token.mutation.graphql';
|
||||
import updateCurrentIntergrationMutation from '../graphql/mutations/update_current_intergration.mutation.graphql';
|
||||
import IntegrationsList from './alerts_integrations_list.vue';
|
||||
import SettingsFormNew from './alerts_settings_form_new.vue';
|
||||
import AlertSettingsForm from './alerts_settings_form.vue';
|
||||
import service from '../services';
|
||||
import { typeSet } from '../constants';
|
||||
import {
|
||||
updateStoreAfterIntegrationDelete,
|
||||
|
@ -35,6 +36,9 @@ export default {
|
|||
'AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list.',
|
||||
),
|
||||
integrationRemoved: s__('AlertsIntegrations|The integration has been successfully removed.'),
|
||||
alertSent: s__(
|
||||
'AlertsIntegrations|The test alert has been successfully sent, and should now be visible on your alerts list.',
|
||||
),
|
||||
},
|
||||
components: {
|
||||
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
|
||||
|
@ -42,7 +46,7 @@ export default {
|
|||
GlLink,
|
||||
GlSprintf,
|
||||
IntegrationsList,
|
||||
SettingsFormNew,
|
||||
AlertSettingsForm,
|
||||
},
|
||||
inject: {
|
||||
generic: {
|
||||
|
@ -89,6 +93,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
isUpdating: false,
|
||||
testAlertPayload: null,
|
||||
integrations: {},
|
||||
currentIntegration: null,
|
||||
};
|
||||
|
@ -131,6 +136,19 @@ export default {
|
|||
if (error) {
|
||||
return createFlash({ message: error });
|
||||
}
|
||||
|
||||
if (this.testAlertPayload) {
|
||||
const integration =
|
||||
httpIntegrationCreate?.integration || prometheusIntegrationCreate?.integration;
|
||||
|
||||
const payload = {
|
||||
...this.testAlertPayload,
|
||||
endpoint: integration.url,
|
||||
token: integration.token,
|
||||
};
|
||||
return this.validateAlertPayload(payload);
|
||||
}
|
||||
|
||||
return createFlash({
|
||||
message: this.$options.i18n.changesSaved,
|
||||
type: FLASH_TYPES.SUCCESS,
|
||||
|
@ -161,6 +179,13 @@ export default {
|
|||
if (error) {
|
||||
return createFlash({ message: error });
|
||||
}
|
||||
|
||||
if (this.testAlertPayload) {
|
||||
return this.validateAlertPayload();
|
||||
}
|
||||
|
||||
this.clearCurrentIntegration();
|
||||
|
||||
return createFlash({
|
||||
message: this.$options.i18n.changesSaved,
|
||||
type: FLASH_TYPES.SUCCESS,
|
||||
|
@ -171,6 +196,7 @@ export default {
|
|||
})
|
||||
.finally(() => {
|
||||
this.isUpdating = false;
|
||||
this.testAlertPayload = null;
|
||||
});
|
||||
},
|
||||
resetToken({ type, variables }) {
|
||||
|
@ -194,7 +220,13 @@ export default {
|
|||
const integration =
|
||||
httpIntegrationResetToken?.integration ||
|
||||
prometheusIntegrationResetToken?.integration;
|
||||
this.currentIntegration = integration;
|
||||
|
||||
this.$apollo.mutate({
|
||||
mutation: updateCurrentIntergrationMutation,
|
||||
variables: {
|
||||
...integration,
|
||||
},
|
||||
});
|
||||
|
||||
return createFlash({
|
||||
message: this.$options.i18n.changesSaved,
|
||||
|
@ -262,8 +294,21 @@ export default {
|
|||
variables: {},
|
||||
});
|
||||
},
|
||||
testPayloadFailure() {
|
||||
createFlash({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
|
||||
setTestAlertPayload(payload) {
|
||||
this.testAlertPayload = payload;
|
||||
},
|
||||
validateAlertPayload(payload) {
|
||||
return service
|
||||
.updateTestAlert(payload ?? this.testAlertPayload)
|
||||
.then(() => {
|
||||
return createFlash({
|
||||
message: this.$options.i18n.alertSent,
|
||||
type: FLASH_TYPES.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -297,7 +342,7 @@ export default {
|
|||
@edit-integration="editIntegration"
|
||||
@delete-integration="deleteIntegration"
|
||||
/>
|
||||
<settings-form-new
|
||||
<alert-settings-form
|
||||
:loading="isUpdating"
|
||||
:can-add-integration="canAddIntegration"
|
||||
:can-manage-opsgenie="canManageOpsgenie"
|
||||
|
@ -305,7 +350,7 @@ export default {
|
|||
@update-integration="updateIntegration"
|
||||
@reset-token="resetToken"
|
||||
@clear-current-integration="clearCurrentIntegration"
|
||||
@test-payload-failure="testPayloadFailure"
|
||||
@set-test-alert-payload="setTestAlertPayload"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -38,6 +38,8 @@ module Types
|
|||
feature_flag: :user_group_counts
|
||||
field :status, Types::UserStatusType, null: true,
|
||||
description: 'User status'
|
||||
field :location, ::GraphQL::STRING_TYPE, null: true,
|
||||
description: 'The location of the user.'
|
||||
field :project_memberships, Types::ProjectMemberType.connection_type, null: true,
|
||||
description: 'Project memberships of the user',
|
||||
method: :project_members
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
module Terraform
|
||||
class State < ApplicationRecord
|
||||
include UsageStatistics
|
||||
include FileStoreMounter
|
||||
include IgnorableColumns
|
||||
# These columns are being removed since geo replication falls to the versioned state
|
||||
# Tracking in https://gitlab.com/gitlab-org/gitlab/-/issues/258262
|
||||
|
@ -36,18 +35,8 @@ module Terraform
|
|||
|
||||
default_value_for(:uuid, allows_nil: false) { SecureRandom.hex(UUID_LENGTH / 2) }
|
||||
|
||||
mount_file_store_uploader StateUploader
|
||||
|
||||
def file_store
|
||||
super || StateUploader.default_store
|
||||
end
|
||||
|
||||
def latest_file
|
||||
if versioning_enabled?
|
||||
latest_version&.file
|
||||
else
|
||||
latest_version&.file || file
|
||||
end
|
||||
latest_version&.file
|
||||
end
|
||||
|
||||
def locked?
|
||||
|
@ -55,13 +44,14 @@ module Terraform
|
|||
end
|
||||
|
||||
def update_file!(data, version:, build:)
|
||||
# This check is required to maintain backwards compatibility with
|
||||
# states that were created prior to versioning being supported.
|
||||
# This can be removed in 14.0 when support for these states is dropped.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/258960
|
||||
if versioning_enabled?
|
||||
create_new_version!(data: data, version: version, build: build)
|
||||
elsif latest_version.present?
|
||||
migrate_legacy_version!(data: data, version: version, build: build)
|
||||
else
|
||||
self.file = data
|
||||
save!
|
||||
migrate_legacy_version!(data: data, version: version, build: build)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ module Terraform
|
|||
|
||||
scope :ordered_by_version_desc, -> { order(version: :desc) }
|
||||
|
||||
default_value_for(:file_store) { VersionedStateUploader.default_store }
|
||||
default_value_for(:file_store) { StateUploader.default_store }
|
||||
|
||||
mount_file_store_uploader VersionedStateUploader
|
||||
mount_file_store_uploader StateUploader
|
||||
|
||||
delegate :project_id, :uuid, to: :terraform_state, allow_nil: true
|
||||
|
||||
|
|
|
@ -587,11 +587,13 @@ class User < ApplicationRecord
|
|||
|
||||
sanitized_order_sql = Arel.sql(sanitize_sql_array([order, query: query]))
|
||||
|
||||
where(
|
||||
fuzzy_arel_match(:name, query, lower_exact_match: true)
|
||||
.or(fuzzy_arel_match(:username, query, lower_exact_match: true))
|
||||
.or(arel_table[:email].eq(query))
|
||||
).reorder(sanitized_order_sql, :name)
|
||||
search_query = if Feature.enabled?(:user_search_secondary_email)
|
||||
search_with_secondary_emails(query)
|
||||
else
|
||||
search_without_secondary_emails(query)
|
||||
end
|
||||
|
||||
search_query.reorder(sanitized_order_sql, :name)
|
||||
end
|
||||
|
||||
# Limits the result set to users _not_ in the given query/list of IDs.
|
||||
|
@ -606,6 +608,18 @@ class User < ApplicationRecord
|
|||
reorder(:name)
|
||||
end
|
||||
|
||||
def search_without_secondary_emails(query)
|
||||
return none if query.blank?
|
||||
|
||||
query = query.downcase
|
||||
|
||||
where(
|
||||
fuzzy_arel_match(:name, query, lower_exact_match: true)
|
||||
.or(fuzzy_arel_match(:username, query, lower_exact_match: true))
|
||||
.or(arel_table[:email].eq(query))
|
||||
)
|
||||
end
|
||||
|
||||
# searches user by given pattern
|
||||
# it compares name, email, username fields and user's secondary emails with given pattern
|
||||
# This method uses ILIKE on PostgreSQL.
|
||||
|
@ -616,15 +630,16 @@ class User < ApplicationRecord
|
|||
query = query.downcase
|
||||
|
||||
email_table = Email.arel_table
|
||||
matched_by_emails_user_ids = email_table
|
||||
matched_by_email_user_id = email_table
|
||||
.project(email_table[:user_id])
|
||||
.where(email_table[:email].eq(query))
|
||||
.take(1) # at most 1 record as there is a unique constraint
|
||||
|
||||
where(
|
||||
fuzzy_arel_match(:name, query)
|
||||
.or(fuzzy_arel_match(:username, query))
|
||||
.or(arel_table[:email].eq(query))
|
||||
.or(arel_table[:id].in(matched_by_emails_user_ids))
|
||||
.or(arel_table[:id].eq(matched_by_email_user_id))
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -7,8 +7,9 @@ module Users
|
|||
end
|
||||
|
||||
def execute(user)
|
||||
return error(_('You are not allowed to approve a user')) unless allowed?
|
||||
return error(_('The user you are trying to approve is not pending an approval')) unless approval_required?(user)
|
||||
return error(_('You are not allowed to approve a user'), :forbidden) unless allowed?
|
||||
return error(_('The user you are trying to approve is not pending an approval'), :conflict) if user.active?
|
||||
return error(_('The user you are trying to approve is not pending an approval'), :conflict) unless approval_required?(user)
|
||||
|
||||
if user.activate
|
||||
# Resends confirmation email if the user isn't confirmed yet.
|
||||
|
@ -18,9 +19,9 @@ module Users
|
|||
DeviseMailer.user_admin_approval(user).deliver_later
|
||||
|
||||
after_approve_hook(user)
|
||||
success
|
||||
success(message: 'Success', http_status: :created)
|
||||
else
|
||||
error(user.errors.full_messages.uniq.join('. '))
|
||||
error(user.errors.full_messages.uniq.join('. '), :unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,17 +6,33 @@ module Terraform
|
|||
|
||||
storage_options Gitlab.config.terraform_state
|
||||
|
||||
delegate :project_id, to: :model
|
||||
delegate :terraform_state, :project_id, to: :model
|
||||
|
||||
# Use Lockbox to encrypt/decrypt the stored file (registers CarrierWave callbacks)
|
||||
encrypt(key: :key)
|
||||
|
||||
def filename
|
||||
"#{model.uuid}.tfstate"
|
||||
# This check is required to maintain backwards compatibility with
|
||||
# states that were created prior to versioning being supported.
|
||||
# This can be removed in 14.0 when support for these states is dropped.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/258960
|
||||
if terraform_state.versioning_enabled?
|
||||
"#{model.version}.tfstate"
|
||||
else
|
||||
"#{model.uuid}.tfstate"
|
||||
end
|
||||
end
|
||||
|
||||
def store_dir
|
||||
project_id.to_s
|
||||
# This check is required to maintain backwards compatibility with
|
||||
# states that were created prior to versioning being supported.
|
||||
# This can be removed in 14.0 when support for these states is dropped.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/258960
|
||||
if terraform_state.versioning_enabled?
|
||||
Gitlab::HashedPath.new(model.uuid, root_hash: project_id)
|
||||
else
|
||||
project_id.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def key
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Terraform
|
||||
class VersionedStateUploader < StateUploader
|
||||
delegate :terraform_state, to: :model
|
||||
|
||||
def filename
|
||||
if terraform_state.versioning_enabled?
|
||||
"#{model.version}.tfstate"
|
||||
else
|
||||
"#{model.uuid}.tfstate"
|
||||
end
|
||||
end
|
||||
|
||||
def store_dir
|
||||
if terraform_state.versioning_enabled?
|
||||
Gitlab::HashedPath.new(model.uuid, root_hash: project_id)
|
||||
else
|
||||
project_id.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add secondary indexes to partitioned audit_events
|
||||
merge_request: 48270
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add API endoint for Administrators to approve pending users
|
||||
merge_request: 47564
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add User.location field to GraphQL API
|
||||
merge_request: 48059
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Update alert setting form to handle JSON payload submit when mapping builder
|
||||
is not enabled
|
||||
merge_request: 48231
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow secondary emails in user search
|
||||
merge_request: 47587
|
||||
author:
|
||||
type: added
|
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
name: ci_variable_expansion_in_rules_changes
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45037
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267192
|
||||
name: user_search_secondary_email
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47587
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/282137
|
||||
milestone: '13.7'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: true
|
||||
group: group::access
|
||||
default_enabled: false
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddPartitionedAuditEventIndexes < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::PartitioningMigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
CREATED_AT_AUTHOR_ID_INDEX_NAME = 'analytics_index_audit_events_part_on_created_at_and_author_id'
|
||||
ENTITY_ID_DESC_INDEX_NAME = 'idx_audit_events_part_on_entity_id_desc_author_id_created_at'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_partitioned_index :audit_events_part_5fc467ac26,
|
||||
[:created_at, :author_id],
|
||||
name: CREATED_AT_AUTHOR_ID_INDEX_NAME
|
||||
|
||||
add_concurrent_partitioned_index :audit_events_part_5fc467ac26,
|
||||
[:entity_id, :entity_type, :id, :author_id, :created_at],
|
||||
order: { id: :desc },
|
||||
name: ENTITY_ID_DESC_INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_partitioned_index_by_name :audit_events_part_5fc467ac26, ENTITY_ID_DESC_INDEX_NAME
|
||||
|
||||
remove_concurrent_partitioned_index_by_name :audit_events_part_5fc467ac26, CREATED_AT_AUTHOR_ID_INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
d8d774e788eeaaecbda3cb7c5530926e74843d844bfad27b6a6e65bf5f89ac8a
|
|
@ -20038,6 +20038,8 @@ CREATE INDEX active_billable_users ON users USING btree (id) WHERE (((state)::te
|
|||
|
||||
CREATE INDEX analytics_index_audit_events_on_created_at_and_author_id ON audit_events USING btree (created_at, author_id);
|
||||
|
||||
CREATE INDEX analytics_index_audit_events_part_on_created_at_and_author_id ON ONLY audit_events_part_5fc467ac26 USING btree (created_at, author_id);
|
||||
|
||||
CREATE INDEX analytics_index_events_on_created_at_and_author_id ON events USING btree (created_at, author_id);
|
||||
|
||||
CREATE INDEX analytics_repository_languages_on_project_id ON analytics_language_trend_repository_languages USING btree (project_id);
|
||||
|
@ -20082,6 +20084,8 @@ CREATE INDEX finding_links_on_vulnerability_occurrence_id ON vulnerability_findi
|
|||
|
||||
CREATE INDEX idx_audit_events_on_entity_id_desc_author_id_created_at ON audit_events USING btree (entity_id, entity_type, id DESC, author_id, created_at);
|
||||
|
||||
CREATE INDEX idx_audit_events_part_on_entity_id_desc_author_id_created_at ON ONLY audit_events_part_5fc467ac26 USING btree (entity_id, entity_type, id DESC, author_id, created_at);
|
||||
|
||||
CREATE INDEX idx_ci_pipelines_artifacts_locked ON ci_pipelines USING btree (ci_ref_id, id) WHERE (locked = 1);
|
||||
|
||||
CREATE INDEX idx_container_exp_policies_on_project_id_next_run_at_enabled ON container_expiration_policies USING btree (project_id, next_run_at, enabled);
|
||||
|
|
|
@ -23030,6 +23030,11 @@ type User {
|
|||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
The location of the user.
|
||||
"""
|
||||
location: String
|
||||
|
||||
"""
|
||||
Human-readable name of the user
|
||||
"""
|
||||
|
|
|
@ -66794,6 +66794,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "location",
|
||||
"description": "The location of the user.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Human-readable name of the user",
|
||||
|
|
|
@ -3450,6 +3450,7 @@ Autogenerated return type of UpdateSnippet.
|
|||
| `groupCount` | Int | Group count for the user. Available only when feature flag `user_group_counts` is enabled |
|
||||
| `groupMemberships` | GroupMemberConnection | Group memberships of the user |
|
||||
| `id` | ID! | ID of the user |
|
||||
| `location` | String | The location of the user. |
|
||||
| `name` | String! | Human-readable name of the user |
|
||||
| `projectMemberships` | ProjectMemberConnection | Project memberships of the user |
|
||||
| `snippets` | SnippetConnection | Snippets authored by the user |
|
||||
|
|
|
@ -1275,8 +1275,8 @@ Parameters:
|
|||
Returns:
|
||||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if user cannot be found.
|
||||
- `403 Forbidden` when trying to activate a user blocked by admin or by LDAP synchronization.
|
||||
- `404 User Not Found` if the user cannot be found.
|
||||
- `403 Forbidden` if the user cannot be activated because they are blocked by an administrator or by LDAP synchronization.
|
||||
|
||||
### Get user contribution events
|
||||
|
||||
|
@ -1337,6 +1337,44 @@ Example response:
|
|||
]
|
||||
```
|
||||
|
||||
## Approve user
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/263107) in GitLab 13.7.
|
||||
|
||||
Approves the specified user. Available only for administrators.
|
||||
|
||||
```plaintext
|
||||
POST /users/:id/approve
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- `id` (required) - ID of specified user
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/users/42/approve"
|
||||
```
|
||||
|
||||
Returns:
|
||||
|
||||
- `201 OK` on success.
|
||||
- `404 User Not Found` if user cannot be found.
|
||||
- `403 Forbidden` if the user cannot be approved because they are blocked by an administrator or by LDAP synchronization.
|
||||
|
||||
Example Responses:
|
||||
|
||||
```json
|
||||
{ "message": "Success" }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "message": "404 User Not Found" }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "message": "The user you are trying to approve is not pending an approval" }
|
||||
```
|
||||
|
||||
## Get an impersonation token of a user
|
||||
|
||||
> Requires admin permissions.
|
||||
|
|
|
@ -1342,14 +1342,7 @@ if there is no `if:` statement that limits the job to branch or merge request pi
|
|||
##### Variables in `rules:changes`
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34272) in GitLab 13.6.
|
||||
> - It was [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
|
||||
> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/267192) in GitLab 13.6.
|
||||
> - It's enabled on GitLab.com.
|
||||
> - It's recommended for production use.
|
||||
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-variables-support-in-ruleschanges). **(CORE ONLY)**
|
||||
|
||||
CAUTION: **Warning:**
|
||||
This feature might not be available to you. Check the **version history** note above for details.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/267192) in GitLab 13.7.
|
||||
|
||||
Environment variables can be used in `rules:changes` expressions to determine when
|
||||
to add jobs to a pipeline:
|
||||
|
@ -1368,25 +1361,6 @@ The `$` character can be used for both variables and paths. For example, if the
|
|||
`$DOCKERFILES_DIR` variable exists, its value is used. If it does not exist, the
|
||||
`$` is interpreted as being part of a path.
|
||||
|
||||
###### Enable or disable variables support in `rules:changes` **(CORE ONLY)**
|
||||
|
||||
Variables support in `rules:changes` is under development, but is ready for production use.
|
||||
It is deployed behind a feature flag that is **enabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
|
||||
can opt to disable it.
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:ci_variable_expansion_in_rules_changes)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:ci_variable_expansion_in_rules_changes)
|
||||
```
|
||||
|
||||
#### `rules:exists`
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24021) in GitLab 12.4.
|
||||
|
|
|
@ -257,19 +257,22 @@ Users of GitLab 12.1 and earlier should use the command `gitlab-rake gitlab:back
|
|||
|
||||
#### Excluding specific directories from the backup
|
||||
|
||||
You can choose what should be exempt from the backup by adding the environment
|
||||
variable `SKIP`. The available options are:
|
||||
You can exclude specific directories from the backup by adding the environment variable `SKIP`, whose values are a comma-separated list of the following options:
|
||||
|
||||
- `db` (database)
|
||||
- `uploads` (attachments)
|
||||
- `repositories` (Git repositories data)
|
||||
- `builds` (CI job output logs)
|
||||
- `artifacts` (CI job artifacts)
|
||||
- `lfs` (LFS objects)
|
||||
- `registry` (Container Registry images)
|
||||
- `pages` (Pages content)
|
||||
- `repositories` (Git repositories data)
|
||||
|
||||
Use a comma to specify several options at the same time:
|
||||
All wikis will be backed up as part of the `repositories` group. Non-existent wikis will be skipped during a backup.
|
||||
|
||||
NOTE: **Note:**
|
||||
When [backing up and restoring Helm Charts](https://docs.gitlab.com/charts/architecture/backup-restore.html), there is an additional option `packages`, which refers to any packages managed by the GitLab [package registry](../user/packages/package_registry/index.md).
|
||||
For more information see [command line arguments](https://docs.gitlab.com/charts/architecture/backup-restore.html#command-line-arguments).
|
||||
|
||||
All wikis are backed up as part of the `repositories` group. Non-existent
|
||||
wikis are skipped during a backup.
|
||||
|
|
|
@ -283,3 +283,25 @@ Support for custom certificate authorities was introduced in the following versi
|
|||
### Getting warning message `gl-secret-detection-report.json: no matching files`
|
||||
|
||||
For information on this, see the [general Application Security troubleshooting section](../../../ci/pipelines/job_artifacts.md#error-message-no-files-to-upload).
|
||||
|
||||
### Error: `Couldn't run the gitleaks command: exit status 2`
|
||||
|
||||
This error is usually caused by the `GIT_DEPTH` value of 50 that is set for all [projects by default](../../../ci/pipelines/settings.md#git-shallow-clone).
|
||||
|
||||
For example, if a pipeline is triggered from a Merge Request containing 60 commits while the `GIT_DEPTH` is set to 50, the Secret Detection job will fail as the clone will not have been deep enough to contain all of the relevant commits.
|
||||
|
||||
You can confirm this to be the cause of the error by implementing a [logging level](../../application_security/secret_detection/index.md#logging-level) of `debug`. Once implemented, the logs should look similar to the following example, wherein an "object not found" error can be seen:
|
||||
|
||||
```plaintext
|
||||
ERRO[2020-11-18T18:05:52Z] object not found
|
||||
[ERRO] [secrets] [2020-11-18T18:05:52Z] ▶ Couldn't run the gitleaks command: exit status 2
|
||||
[ERRO] [secrets] [2020-11-18T18:05:52Z] ▶ Gitleaks analysis failed: exit status 2
|
||||
```
|
||||
|
||||
If this is the case, we can resolve the issue by setting the [`GIT_DEPTH` variable](../../../ci/runners/README.md#shallow-cloning) to a higher value. In order to apply this only to the Secret Detection job, the following can be added to your `.gitlab-ci.yml`:
|
||||
|
||||
```yaml
|
||||
secret_detection:
|
||||
variables:
|
||||
GIT_DEPTH: 100
|
||||
```
|
||||
|
|
|
@ -534,6 +534,24 @@ module API
|
|||
|
||||
user.activate
|
||||
end
|
||||
|
||||
desc 'Approve a pending user. Available only for admins.'
|
||||
params do
|
||||
requires :id, type: Integer, desc: 'The ID of the user'
|
||||
end
|
||||
post ':id/approve', feature_category: :authentication_and_authorization do
|
||||
user = User.find_by(id: params[:id])
|
||||
not_found!('User') unless can?(current_user, :read_user, user)
|
||||
|
||||
result = ::Users::ApproveService.new(current_user).execute(user)
|
||||
|
||||
if result[:success]
|
||||
result
|
||||
else
|
||||
render_api_error!(result[:message], result[:http_status])
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
desc 'Deactivate an active user. Available only for admins.'
|
||||
params do
|
||||
|
|
|
@ -11,7 +11,7 @@ module Gitlab
|
|||
def satisfied_by?(pipeline, context)
|
||||
return true if pipeline.modified_paths.nil?
|
||||
|
||||
expanded_globs = expand_globs(pipeline, context)
|
||||
expanded_globs = expand_globs(context)
|
||||
pipeline.modified_paths.any? do |path|
|
||||
expanded_globs.any? do |glob|
|
||||
File.fnmatch?(glob, path, File::FNM_PATHNAME | File::FNM_DOTMATCH | File::FNM_EXTGLOB)
|
||||
|
@ -19,8 +19,7 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def expand_globs(pipeline, context)
|
||||
return @globs unless ::Feature.enabled?(:ci_variable_expansion_in_rules_changes, pipeline.project, default_enabled: true)
|
||||
def expand_globs(context)
|
||||
return @globs unless context
|
||||
|
||||
@globs.map do |glob|
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.auto-deploy:
|
||||
image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v2.0.0-beta.2"
|
||||
image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v2.0.0"
|
||||
dependencies: []
|
||||
|
||||
review:
|
||||
|
|
|
@ -2733,6 +2733,9 @@ msgstr ""
|
|||
msgid "AlertsIntegrations|The integration token could not be reset. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "AlertsIntegrations|The test alert has been successfully sent, and should now be visible on your alerts list."
|
||||
msgstr ""
|
||||
|
||||
msgid "AlertsIntegrations|You have opted to delete the %{integrationName} integration. Do you want to proceed? It means you will no longer receive alerts from this endpoint in your alert list, and this action cannot be undone."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Lint
|
||||
# This cop only works if there are files from deprecation_toolkit. You can
|
||||
# generate these files by:
|
||||
#
|
||||
# 1. Running specs with RECORD_DEPRECATIONS=1
|
||||
# 1. Downloading the complete set of deprecations/ files from a CI
|
||||
# pipeline (see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47720)
|
||||
class LastKeywordArgument < Cop
|
||||
MSG = 'Using the last argument as keyword parameters is deprecated'.freeze
|
||||
|
||||
DEPRECATIONS_GLOB = File.expand_path('../../../deprecations/**/*.yml', __dir__)
|
||||
KEYWORD_DEPRECATION_STR = 'maybe ** should be added to the call'
|
||||
|
||||
def on_send(node)
|
||||
arg = node.arguments.last
|
||||
return unless arg
|
||||
|
||||
return unless known_match?(processed_source.file_path, node.first_line, node.method_name.to_s)
|
||||
|
||||
return if arg.children.first.respond_to?(:kwsplat_type?) && arg.children.first&.kwsplat_type?
|
||||
|
||||
# parser thinks `a: :b, c: :d` is hash type, it's actually kwargs
|
||||
return if arg.hash_type? && !arg.source.match(/\A{/)
|
||||
|
||||
add_offense(arg)
|
||||
end
|
||||
|
||||
def autocorrect(arg)
|
||||
lambda do |corrector|
|
||||
if arg.hash_type?
|
||||
kwarg = arg.source.sub(/\A{\s*/, '').sub(/\s*}\z/, '')
|
||||
corrector.replace(arg, kwarg)
|
||||
elsif arg.splat_type?
|
||||
corrector.insert_before(arg, '*')
|
||||
else
|
||||
corrector.insert_before(arg, '**')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def known_match?(file_path, line_number, method_name)
|
||||
file_path_from_root = file_path.sub(File.expand_path('../../..', __dir__), '')
|
||||
|
||||
self.class.keyword_warnings.any? do |warning|
|
||||
warning.include?("#{file_path_from_root}:#{line_number}") && warning.include?("called method `#{method_name}'")
|
||||
end
|
||||
end
|
||||
|
||||
def self.keyword_warnings
|
||||
@keyword_warnings ||= keywords_list
|
||||
end
|
||||
|
||||
def self.keywords_list
|
||||
hash = Dir.glob(DEPRECATIONS_GLOB).each_with_object({}) do |file, hash|
|
||||
hash.merge!(YAML.safe_load(File.read(file)))
|
||||
end
|
||||
|
||||
hash.values.flatten.select { |str| str.include?(KEYWORD_DEPRECATION_STR) }.uniq
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
if ENV.key?('RECORD_DEPRECATIONS')
|
||||
require 'deprecation_toolkit'
|
||||
require 'deprecation_toolkit/rspec'
|
||||
DeprecationToolkit::Configuration.test_runner = :rspec
|
||||
DeprecationToolkit::Configuration.deprecation_path = 'deprecations'
|
||||
DeprecationToolkit::Configuration.behavior = DeprecationToolkit::Behaviors::Record
|
||||
|
||||
# Enable ruby deprecations for keywords, it's suppressed by default in Ruby 2.7.2
|
||||
Warning[:deprecated] = true
|
||||
|
||||
kwargs_warnings = [
|
||||
# Taken from https://github.com/jeremyevans/ruby-warning/blob/1.1.0/lib/warning.rb#L18
|
||||
%r{warning: (?:Using the last argument (?:for `.+' )?as keyword parameters is deprecated; maybe \*\* should be added to the call|Passing the keyword argument (?:for `.+' )?as the last hash parameter is deprecated|Splitting the last argument (?:for `.+' )?into positional and keyword parameters is deprecated|The called method (?:`.+' )?is defined here)\n\z}
|
||||
]
|
||||
DeprecationToolkit::Configuration.warnings_treated_as_deprecation = kwargs_warnings
|
||||
end
|
|
@ -6,11 +6,6 @@ FactoryBot.define do
|
|||
|
||||
sequence(:name) { |n| "state-#{n}" }
|
||||
|
||||
trait :with_file do
|
||||
versioning_enabled { false }
|
||||
file { fixture_file_upload('spec/fixtures/terraform/terraform.tfstate', 'application/json') }
|
||||
end
|
||||
|
||||
trait :locked do
|
||||
sequence(:lock_xid) { |n| "lock-#{n}" }
|
||||
locked_at { Time.current }
|
||||
|
@ -22,8 +17,5 @@ FactoryBot.define do
|
|||
create(:terraform_state_version, terraform_state: state)
|
||||
end
|
||||
end
|
||||
|
||||
# Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/235108
|
||||
factory :legacy_terraform_state, parent: :terraform_state, traits: [:with_file]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,6 +35,10 @@ FactoryBot.define do
|
|||
user_type { :alert_bot }
|
||||
end
|
||||
|
||||
trait :deactivated do
|
||||
after(:build) { |user, _| user.deactivate! }
|
||||
end
|
||||
|
||||
trait :project_bot do
|
||||
user_type { :project_bot }
|
||||
end
|
||||
|
|
|
@ -87,7 +87,7 @@ exports[`AlertsSettingsFormNew with default values renders the initial template
|
|||
<div class=\\"gl-display-flex gl-justify-content-start gl-py-3\\"><button data-testid=\\"integration-form-submit\\" type=\\"submit\\" class=\\"btn js-no-auto-disable btn-success btn-md gl-button\\">
|
||||
<!---->
|
||||
<!----> <span class=\\"gl-button-text\\">Save integration
|
||||
</span></button> <button data-testid=\\"integration-test-and-submit\\" type=\\"button\\" class=\\"btn gl-mx-3 js-no-auto-disable btn-success btn-md gl-button btn-success-secondary\\">
|
||||
</span></button> <button data-testid=\\"integration-test-and-submit\\" type=\\"button\\" disabled=\\"disabled\\" class=\\"btn gl-mx-3 js-no-auto-disable btn-success btn-md disabled gl-button btn-success-secondary\\">
|
||||
<!---->
|
||||
<!----> <span class=\\"gl-button-text\\">Save and test payload</span></button> <button type=\\"reset\\" class=\\"btn js-no-auto-disable btn-default btn-md gl-button\\">
|
||||
<!---->
|
|
@ -8,7 +8,7 @@ import {
|
|||
GlFormTextarea,
|
||||
} from '@gitlab/ui';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form_new.vue';
|
||||
import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
|
||||
import { defaultAlertSettingsConfig } from './util';
|
||||
import { typeSet } from '~/alerts_settings/constants';
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
import VueApollo from 'vue-apollo';
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||
import createMockApollo from 'jest/helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
|
||||
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
|
||||
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue';
|
||||
import AlertsSettingsFormNew from '~/alerts_settings/components/alerts_settings_form_new.vue';
|
||||
import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
|
||||
import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue';
|
||||
import getIntegrationsQuery from '~/alerts_settings/graphql/queries/get_integrations.query.graphql';
|
||||
import createHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql';
|
||||
|
@ -113,17 +115,15 @@ describe('AlertsSettingsWrapper', () => {
|
|||
}
|
||||
|
||||
afterEach(() => {
|
||||
if (wrapper) {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
}
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('rendered via default permissions', () => {
|
||||
it('renders the GraphQL alerts integrations list and new form', () => {
|
||||
createComponent();
|
||||
expect(wrapper.find(IntegrationsList).exists()).toBe(true);
|
||||
expect(wrapper.find(AlertsSettingsFormNew).exists()).toBe(true);
|
||||
expect(wrapper.find(AlertsSettingsForm).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('uses a loading state inside the IntegrationsList table', () => {
|
||||
|
@ -153,7 +153,7 @@ describe('AlertsSettingsWrapper', () => {
|
|||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
|
||||
data: { createHttpIntegrationMutation: { integration: { id: '1' } } },
|
||||
});
|
||||
wrapper.find(AlertsSettingsFormNew).vm.$emit('create-new-integration', {
|
||||
wrapper.find(AlertsSettingsForm).vm.$emit('create-new-integration', {
|
||||
type: typeSet.http,
|
||||
variables: createHttpVariables,
|
||||
});
|
||||
|
@ -175,7 +175,7 @@ describe('AlertsSettingsWrapper', () => {
|
|||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
|
||||
data: { updateHttpIntegrationMutation: { integration: { id: '1' } } },
|
||||
});
|
||||
wrapper.find(AlertsSettingsFormNew).vm.$emit('update-integration', {
|
||||
wrapper.find(AlertsSettingsForm).vm.$emit('update-integration', {
|
||||
type: typeSet.http,
|
||||
variables: updateHttpVariables,
|
||||
});
|
||||
|
@ -195,7 +195,7 @@ describe('AlertsSettingsWrapper', () => {
|
|||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
|
||||
data: { resetHttpTokenMutation: { integration: { id: '1' } } },
|
||||
});
|
||||
wrapper.find(AlertsSettingsFormNew).vm.$emit('reset-token', {
|
||||
wrapper.find(AlertsSettingsForm).vm.$emit('reset-token', {
|
||||
type: typeSet.http,
|
||||
variables: { id: ID },
|
||||
});
|
||||
|
@ -217,7 +217,7 @@ describe('AlertsSettingsWrapper', () => {
|
|||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
|
||||
data: { createPrometheusIntegrationMutation: { integration: { id: '2' } } },
|
||||
});
|
||||
wrapper.find(AlertsSettingsFormNew).vm.$emit('create-new-integration', {
|
||||
wrapper.find(AlertsSettingsForm).vm.$emit('create-new-integration', {
|
||||
type: typeSet.prometheus,
|
||||
variables: createPrometheusVariables,
|
||||
});
|
||||
|
@ -239,7 +239,7 @@ describe('AlertsSettingsWrapper', () => {
|
|||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
|
||||
data: { updatePrometheusIntegrationMutation: { integration: { id: '2' } } },
|
||||
});
|
||||
wrapper.find(AlertsSettingsFormNew).vm.$emit('update-integration', {
|
||||
wrapper.find(AlertsSettingsForm).vm.$emit('update-integration', {
|
||||
type: typeSet.prometheus,
|
||||
variables: updatePrometheusVariables,
|
||||
});
|
||||
|
@ -259,7 +259,7 @@ describe('AlertsSettingsWrapper', () => {
|
|||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
|
||||
data: { resetPrometheusTokenMutation: { integration: { id: '1' } } },
|
||||
});
|
||||
wrapper.find(AlertsSettingsFormNew).vm.$emit('reset-token', {
|
||||
wrapper.find(AlertsSettingsForm).vm.$emit('reset-token', {
|
||||
type: typeSet.prometheus,
|
||||
variables: { id: ID },
|
||||
});
|
||||
|
@ -279,7 +279,7 @@ describe('AlertsSettingsWrapper', () => {
|
|||
});
|
||||
|
||||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(ADD_INTEGRATION_ERROR);
|
||||
wrapper.find(AlertsSettingsFormNew).vm.$emit('create-new-integration', {});
|
||||
wrapper.find(AlertsSettingsForm).vm.$emit('create-new-integration', {});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
|
@ -294,7 +294,7 @@ describe('AlertsSettingsWrapper', () => {
|
|||
|
||||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(RESET_INTEGRATION_TOKEN_ERROR);
|
||||
|
||||
wrapper.find(AlertsSettingsFormNew).vm.$emit('reset-token', {});
|
||||
wrapper.find(AlertsSettingsForm).vm.$emit('reset-token', {});
|
||||
|
||||
await waitForPromises();
|
||||
expect(createFlash).toHaveBeenCalledWith({ message: RESET_INTEGRATION_TOKEN_ERROR });
|
||||
|
@ -308,23 +308,25 @@ describe('AlertsSettingsWrapper', () => {
|
|||
|
||||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(errorMsg);
|
||||
|
||||
wrapper.find(AlertsSettingsFormNew).vm.$emit('update-integration', {});
|
||||
wrapper.find(AlertsSettingsForm).vm.$emit('update-integration', {});
|
||||
|
||||
await waitForPromises();
|
||||
expect(createFlash).toHaveBeenCalledWith({ message: UPDATE_INTEGRATION_ERROR });
|
||||
});
|
||||
|
||||
it('shows an error alert when integration test payload fails ', async () => {
|
||||
const mock = new AxiosMockAdapter(axios);
|
||||
mock.onPost(/(.*)/).replyOnce(403);
|
||||
createComponent({
|
||||
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
|
||||
loading: false,
|
||||
});
|
||||
|
||||
wrapper.find(AlertsSettingsFormNew).vm.$emit('test-payload-failure');
|
||||
|
||||
await waitForPromises();
|
||||
expect(createFlash).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
|
||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
||||
return wrapper.vm.validateAlertPayload({ endpoint: '', data: '', token: '' }).then(() => {
|
||||
expect(createFlash).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
|
||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
||||
mock.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -23,12 +23,12 @@ RSpec.describe Mutations::Boards::Lists::Create do
|
|||
|
||||
describe '#ready?' do
|
||||
it 'raises an error if required arguments are missing' do
|
||||
expect { mutation.ready?({ board_id: 'some id' }) }
|
||||
expect { mutation.ready?(board_id: 'some id') }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
|
||||
end
|
||||
|
||||
it 'raises an error if too many required arguments are specified' do
|
||||
expect { mutation.ready?({ board_id: 'some id', backlog: true, label_id: 'some label' }) }
|
||||
expect { mutation.ready?(board_id: 'some id', backlog: true, label_id: 'some label') }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,7 @@ RSpec.describe GitlabSchema.types['User'] do
|
|||
todos
|
||||
state
|
||||
status
|
||||
location
|
||||
authoredMergeRequests
|
||||
assignedMergeRequests
|
||||
groupMemberships
|
||||
|
|
|
@ -3,21 +3,13 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Terraform::State do
|
||||
subject { create(:terraform_state, :with_file) }
|
||||
|
||||
let(:terraform_state_file) { fixture_file('terraform/terraform.tfstate') }
|
||||
|
||||
it { is_expected.to be_a FileStoreMounter }
|
||||
subject { create(:terraform_state, :with_version) }
|
||||
|
||||
it { is_expected.to belong_to(:project) }
|
||||
it { is_expected.to belong_to(:locked_by_user).class_name('User') }
|
||||
|
||||
it { is_expected.to validate_presence_of(:project_id) }
|
||||
|
||||
before do
|
||||
stub_terraform_state_object_storage
|
||||
end
|
||||
|
||||
describe 'scopes' do
|
||||
describe '.ordered_by_name' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
@ -35,64 +27,18 @@ RSpec.describe Terraform::State do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#file' do
|
||||
context 'when a file exists' do
|
||||
it 'does not use the default file' do
|
||||
expect(subject.file.read).to eq(terraform_state_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#file_store' do
|
||||
context 'when a value is set' do
|
||||
it 'returns the value' do
|
||||
[ObjectStorage::Store::LOCAL, ObjectStorage::Store::REMOTE].each do |store|
|
||||
expect(build(:terraform_state, file_store: store).file_store).to eq(store)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_file_store' do
|
||||
context 'when file is stored in object storage' do
|
||||
it_behaves_like 'mounted file in object store'
|
||||
end
|
||||
|
||||
context 'when file is stored locally' do
|
||||
before do
|
||||
stub_terraform_state_object_storage(enabled: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'mounted file in local store'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#latest_file' do
|
||||
let(:terraform_state) { create(:terraform_state, :with_version) }
|
||||
let(:latest_version) { terraform_state.latest_version }
|
||||
|
||||
subject { terraform_state.latest_file }
|
||||
|
||||
context 'versioning is enabled' do
|
||||
let(:terraform_state) { create(:terraform_state, :with_version) }
|
||||
let(:latest_version) { terraform_state.latest_version }
|
||||
it { is_expected.to eq latest_version.file }
|
||||
|
||||
it { is_expected.to eq latest_version.file }
|
||||
context 'but no version exists yet' do
|
||||
let(:terraform_state) { create(:terraform_state) }
|
||||
|
||||
context 'but no version exists yet' do
|
||||
let(:terraform_state) { create(:terraform_state) }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
context 'versioning is disabled' do
|
||||
let(:terraform_state) { create(:terraform_state, :with_file) }
|
||||
|
||||
it { is_expected.to eq terraform_state.file }
|
||||
|
||||
context 'and a version exists (migration to versioned in progress)' do
|
||||
let!(:migrated_version) { create(:terraform_state_version, terraform_state: terraform_state) }
|
||||
|
||||
it { is_expected.to eq terraform_state.latest_version.file }
|
||||
end
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -115,39 +61,30 @@ RSpec.describe Terraform::State do
|
|||
end
|
||||
end
|
||||
|
||||
context 'versioning is disabled' do
|
||||
let(:terraform_state) { create(:terraform_state, :with_file) }
|
||||
context 'versioning is disabled (migration to versioned in progress)' do
|
||||
let(:terraform_state) { create(:terraform_state, versioning_enabled: false) }
|
||||
let!(:migrated_version) { create(:terraform_state_version, terraform_state: terraform_state, version: 0) }
|
||||
|
||||
it 'modifies the existing state record' do
|
||||
expect { subject }.not_to change { Terraform::StateVersion.count }
|
||||
it 'creates a new version, corrects the migrated version number, and marks the state as versioned' do
|
||||
expect { subject }.to change { Terraform::StateVersion.count }
|
||||
|
||||
expect(terraform_state.latest_file.read).to eq(data)
|
||||
expect(migrated_version.reload.version).to eq(1)
|
||||
expect(migrated_version.file.read).to eq(fixture_file('terraform/terraform.tfstate'))
|
||||
|
||||
expect(terraform_state.reload.latest_version.version).to eq(version)
|
||||
expect(terraform_state.latest_version.file.read).to eq(data)
|
||||
expect(terraform_state).to be_versioning_enabled
|
||||
end
|
||||
|
||||
context 'and a version exists (migration to versioned in progress)' do
|
||||
let!(:migrated_version) { create(:terraform_state_version, terraform_state: terraform_state, version: 0) }
|
||||
|
||||
it 'creates a new version, corrects the migrated version number, and marks the state as versioned' do
|
||||
expect { subject }.to change { Terraform::StateVersion.count }
|
||||
|
||||
expect(migrated_version.reload.version).to eq(1)
|
||||
expect(migrated_version.file.read).to eq(terraform_state_file)
|
||||
|
||||
expect(terraform_state.reload.latest_version.version).to eq(version)
|
||||
expect(terraform_state.latest_version.file.read).to eq(data)
|
||||
expect(terraform_state).to be_versioning_enabled
|
||||
context 'the current version cannot be determined' do
|
||||
before do
|
||||
migrated_version.update!(file: CarrierWaveStringFile.new('invalid-json'))
|
||||
end
|
||||
|
||||
context 'the current version cannot be determined' do
|
||||
before do
|
||||
migrated_version.update!(file: CarrierWaveStringFile.new('invalid-json'))
|
||||
end
|
||||
it 'uses version - 1 to correct the migrated version number' do
|
||||
expect { subject }.to change { Terraform::StateVersion.count }
|
||||
|
||||
it 'uses version - 1 to correct the migrated version number' do
|
||||
expect { subject }.to change { Terraform::StateVersion.count }
|
||||
|
||||
expect(migrated_version.reload.version).to eq(2)
|
||||
end
|
||||
expect(migrated_version.reload.version).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2024,9 +2024,10 @@ RSpec.describe User do
|
|||
end
|
||||
|
||||
describe '.search' do
|
||||
let!(:user) { create(:user, name: 'user', username: 'usern', email: 'email@gmail.com') }
|
||||
let!(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@gmail.com') }
|
||||
let!(:user3) { create(:user, name: 'us', username: 'se', email: 'foo@gmail.com') }
|
||||
let_it_be(:user) { create(:user, name: 'user', username: 'usern', email: 'email@example.com') }
|
||||
let_it_be(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@example.com') }
|
||||
let_it_be(:user3) { create(:user, name: 'us', username: 'se', email: 'foo@example.com') }
|
||||
let_it_be(:email) { create(:email, user: user, email: 'alias@example.com') }
|
||||
|
||||
describe 'name matching' do
|
||||
it 'returns users with a matching name with exact match first' do
|
||||
|
@ -2056,7 +2057,7 @@ RSpec.describe User do
|
|||
end
|
||||
|
||||
it 'does not return users with a partially matching Email' do
|
||||
expect(described_class.search(user.email[0..2])).not_to include(user, user2)
|
||||
expect(described_class.search(user.email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'returns users with a matching Email regardless of the casing' do
|
||||
|
@ -2064,6 +2065,36 @@ RSpec.describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'secondary email matching' do
|
||||
context 'feature flag :user_search_secondary_email is enabled' do
|
||||
it 'returns users with a matching secondary email' do
|
||||
expect(described_class.search(email.email)).to include(email.user)
|
||||
end
|
||||
|
||||
it 'does not return users with a matching part of secondary email' do
|
||||
expect(described_class.search(email.email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'returns users with a matching secondary email regardless of the casing' do
|
||||
expect(described_class.search(email.email.upcase)).to include(email.user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'feature flag :user_search_secondary_email is disabled' do
|
||||
before do
|
||||
stub_feature_flags(user_search_secondary_email: false)
|
||||
end
|
||||
|
||||
it 'does not return users with a matching secondary email' do
|
||||
expect(described_class.search(email.email)).not_to include(email.user)
|
||||
end
|
||||
|
||||
it 'does not return users with a matching part of secondary email' do
|
||||
expect(described_class.search(email.email[1...-1])).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'username matching' do
|
||||
it 'returns users with a matching username' do
|
||||
expect(described_class.search(user.username)).to eq([user, user2])
|
||||
|
@ -2103,65 +2134,119 @@ RSpec.describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.search_with_secondary_emails' do
|
||||
delegate :search_with_secondary_emails, to: :described_class
|
||||
|
||||
let!(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'john.doe@example.com' ) }
|
||||
let!(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'albert.smith@example.com' ) }
|
||||
let!(:email) do
|
||||
create(:email, user: another_user, email: 'alias@example.com')
|
||||
end
|
||||
describe '.search_without_secondary_emails' do
|
||||
let_it_be(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'someone.1@example.com' ) }
|
||||
let_it_be(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'another.2@example.com' ) }
|
||||
let_it_be(:email) { create(:email, user: another_user, email: 'alias@example.com') }
|
||||
|
||||
it 'returns users with a matching name' do
|
||||
expect(search_with_secondary_emails(user.name)).to eq([user])
|
||||
expect(described_class.search_without_secondary_emails(user.name)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a partially matching name' do
|
||||
expect(search_with_secondary_emails(user.name[0..2])).to eq([user])
|
||||
expect(described_class.search_without_secondary_emails(user.name[0..2])).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching name regardless of the casing' do
|
||||
expect(search_with_secondary_emails(user.name.upcase)).to eq([user])
|
||||
expect(described_class.search_without_secondary_emails(user.name.upcase)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching email' do
|
||||
expect(search_with_secondary_emails(user.email)).to eq([user])
|
||||
expect(described_class.search_without_secondary_emails(user.email)).to eq([user])
|
||||
end
|
||||
|
||||
it 'does not return users with a partially matching email' do
|
||||
expect(search_with_secondary_emails(user.email[0..2])).not_to include([user])
|
||||
expect(described_class.search_without_secondary_emails(user.email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'returns users with a matching email regardless of the casing' do
|
||||
expect(search_with_secondary_emails(user.email.upcase)).to eq([user])
|
||||
expect(described_class.search_without_secondary_emails(user.email.upcase)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching username' do
|
||||
expect(search_with_secondary_emails(user.username)).to eq([user])
|
||||
expect(described_class.search_without_secondary_emails(user.username)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a partially matching username' do
|
||||
expect(search_with_secondary_emails(user.username[0..2])).to eq([user])
|
||||
expect(described_class.search_without_secondary_emails(user.username[0..2])).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching username regardless of the casing' do
|
||||
expect(search_with_secondary_emails(user.username.upcase)).to eq([user])
|
||||
expect(described_class.search_without_secondary_emails(user.username.upcase)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching whole secondary email' do
|
||||
expect(search_with_secondary_emails(email.email)).to eq([email.user])
|
||||
it 'does not return users with a matching whole secondary email' do
|
||||
expect(described_class.search_without_secondary_emails(email.email)).not_to include(email.user)
|
||||
end
|
||||
|
||||
it 'does not return users with a matching part of secondary email' do
|
||||
expect(search_with_secondary_emails(email.email[1..4])).not_to include([email.user])
|
||||
expect(described_class.search_without_secondary_emails(email.email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'returns no matches for an empty string' do
|
||||
expect(search_with_secondary_emails('')).to be_empty
|
||||
expect(described_class.search_without_secondary_emails('')).to be_empty
|
||||
end
|
||||
|
||||
it 'returns no matches for nil' do
|
||||
expect(search_with_secondary_emails(nil)).to be_empty
|
||||
expect(described_class.search_without_secondary_emails(nil)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '.search_with_secondary_emails' do
|
||||
let_it_be(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'someone.1@example.com' ) }
|
||||
let_it_be(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'another.2@example.com' ) }
|
||||
let_it_be(:email) { create(:email, user: another_user, email: 'alias@example.com') }
|
||||
|
||||
it 'returns users with a matching name' do
|
||||
expect(described_class.search_with_secondary_emails(user.name)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a partially matching name' do
|
||||
expect(described_class.search_with_secondary_emails(user.name[0..2])).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching name regardless of the casing' do
|
||||
expect(described_class.search_with_secondary_emails(user.name.upcase)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching email' do
|
||||
expect(described_class.search_with_secondary_emails(user.email)).to eq([user])
|
||||
end
|
||||
|
||||
it 'does not return users with a partially matching email' do
|
||||
expect(described_class.search_with_secondary_emails(user.email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'returns users with a matching email regardless of the casing' do
|
||||
expect(described_class.search_with_secondary_emails(user.email.upcase)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching username' do
|
||||
expect(described_class.search_with_secondary_emails(user.username)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a partially matching username' do
|
||||
expect(described_class.search_with_secondary_emails(user.username[0..2])).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching username regardless of the casing' do
|
||||
expect(described_class.search_with_secondary_emails(user.username.upcase)).to eq([user])
|
||||
end
|
||||
|
||||
it 'returns users with a matching whole secondary email' do
|
||||
expect(described_class.search_with_secondary_emails(email.email)).to eq([email.user])
|
||||
end
|
||||
|
||||
it 'does not return users with a matching part of secondary email' do
|
||||
expect(described_class.search_with_secondary_emails(email.email[1...-1])).to be_empty
|
||||
end
|
||||
|
||||
it 'returns no matches for an empty string' do
|
||||
expect(described_class.search_with_secondary_emails('')).to be_empty
|
||||
end
|
||||
|
||||
it 'returns no matches for nil' do
|
||||
expect(described_class.search_with_secondary_emails(nil)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2510,6 +2510,98 @@ RSpec.describe API::Users, :do_not_mock_admin_mode do
|
|||
end
|
||||
end
|
||||
|
||||
context 'approve pending user' do
|
||||
shared_examples '404' do
|
||||
it 'returns 404' do
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(json_response['message']).to eq('404 User Not Found')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /users/:id/approve' do
|
||||
subject(:approve) { post api("/users/#{user_id}/approve", api_user) }
|
||||
|
||||
let_it_be(:pending_user) { create(:user, :blocked_pending_approval) }
|
||||
let_it_be(:deactivated_user) { create(:user, :deactivated) }
|
||||
let_it_be(:blocked_user) { create(:user, :blocked) }
|
||||
|
||||
context 'performed by a non-admin user' do
|
||||
let(:api_user) { user }
|
||||
let(:user_id) { pending_user.id }
|
||||
|
||||
it 'is not authorized to perform the action' do
|
||||
expect { approve }.not_to change { pending_user.reload.state }
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(json_response['message']).to eq('You are not allowed to approve a user')
|
||||
end
|
||||
end
|
||||
|
||||
context 'performed by an admin user' do
|
||||
let(:api_user) { admin }
|
||||
|
||||
context 'for a deactivated user' do
|
||||
let(:user_id) { deactivated_user.id }
|
||||
|
||||
it 'does not approve a deactivated user' do
|
||||
expect { approve }.not_to change { deactivated_user.reload.state }
|
||||
expect(response).to have_gitlab_http_status(:conflict)
|
||||
expect(json_response['message']).to eq('The user you are trying to approve is not pending an approval')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for an pending approval user' do
|
||||
let(:user_id) { pending_user.id }
|
||||
|
||||
it 'returns 201' do
|
||||
expect { approve }.to change { pending_user.reload.state }.to('active')
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response['message']).to eq('Success')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for an active user' do
|
||||
let(:user_id) { user.id }
|
||||
|
||||
it 'returns 201' do
|
||||
expect { approve }.not_to change { user.reload.state }
|
||||
expect(response).to have_gitlab_http_status(:conflict)
|
||||
expect(json_response['message']).to eq('The user you are trying to approve is not pending an approval')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a blocked user' do
|
||||
let(:user_id) { blocked_user.id }
|
||||
|
||||
it 'returns 403' do
|
||||
expect { approve }.not_to change { blocked_user.reload.state }
|
||||
expect(response).to have_gitlab_http_status(:conflict)
|
||||
expect(json_response['message']).to eq('The user you are trying to approve is not pending an approval')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a ldap blocked user' do
|
||||
let(:user_id) { ldap_blocked_user.id }
|
||||
|
||||
it 'returns 403' do
|
||||
expect { approve }.not_to change { ldap_blocked_user.reload.state }
|
||||
expect(response).to have_gitlab_http_status(:conflict)
|
||||
expect(json_response['message']).to eq('The user you are trying to approve is not pending an approval')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a user that does not exist' do
|
||||
let(:user_id) { non_existing_record_id }
|
||||
|
||||
before do
|
||||
approve
|
||||
end
|
||||
|
||||
it_behaves_like '404'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /users/:id/block' do
|
||||
let(:blocked_user) { create(:user, state: 'blocked') }
|
||||
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'rubocop'
|
||||
require_relative '../../../../rubocop/cop/lint/last_keyword_argument'
|
||||
|
||||
RSpec.describe RuboCop::Cop::Lint::LastKeywordArgument, type: :rubocop do
|
||||
include CopHelper
|
||||
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
before do
|
||||
described_class.instance_variable_set(:@keyword_warnings, nil)
|
||||
end
|
||||
|
||||
context 'deprecation files does not exist' do
|
||||
before do
|
||||
allow(Dir).to receive(:glob).and_return([])
|
||||
allow(File).to receive(:exist?).and_return(false)
|
||||
end
|
||||
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses(<<~SOURCE)
|
||||
users.call(params)
|
||||
SOURCE
|
||||
end
|
||||
end
|
||||
|
||||
context 'deprecation files does exist' do
|
||||
let(:create_spec_yaml) do
|
||||
<<~YAML
|
||||
---
|
||||
test_mutations/boards/lists/create#resolve_with_proper_permissions_backlog_list_creates_one_and_only_one_backlog:
|
||||
- |
|
||||
DEPRECATION WARNING: /Users/tkuah/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/batch-loader-1.4.0/lib/batch_loader/graphql.rb:38: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
|
||||
/Users/tkuah/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/batch-loader-1.4.0/lib/batch_loader.rb:26: warning: The called method `batch' is defined here
|
||||
test_mutations/boards/lists/create#ready?_raises_an_error_if_required_arguments_are_missing:
|
||||
- |
|
||||
DEPRECATION WARNING: /Users/tkuah/code/ee-gdk/gitlab/create_service.rb:1: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
|
||||
/Users/tkuah/code/ee-gdk/gitlab/user.rb:17: warning: The called method `call' is defined here
|
||||
YAML
|
||||
end
|
||||
|
||||
let(:projects_spec_yaml) do
|
||||
<<~YAML
|
||||
---
|
||||
test_api/projects_get_/projects_when_unauthenticated_behaves_like_projects_response_returns_an_array_of_projects:
|
||||
- |
|
||||
DEPRECATION WARNING: /Users/tkuah/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/state_machines-activerecord-0.6.0/lib/state_machines/integrations/active_record.rb:511: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
|
||||
/Users/tkuah/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/activerecord-6.0.3.3/lib/active_record/suppressor.rb:43: warning: The called method `save' is defined here
|
||||
- |
|
||||
DEPRECATION WARNING: /Users/tkuah/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/rack-2.2.3/lib/rack/builder.rb:158: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
|
||||
/Users/tkuah/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/grape-1.4.0/lib/grape/middleware/error.rb:30: warning: The called method `initialize' is defined here
|
||||
YAML
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Dir).to receive(:glob).and_return(['deprecations/service/create_spec.yml', 'deprecations/api/projects_spec.yml'])
|
||||
allow(File).to receive(:read).and_return(create_spec_yaml, projects_spec_yaml)
|
||||
end
|
||||
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~SOURCE, 'create_service.rb')
|
||||
users.call(params)
|
||||
^^^^^^ Using the last argument as keyword parameters is deprecated
|
||||
SOURCE
|
||||
|
||||
expect_correction(<<~SOURCE)
|
||||
users.call(**params)
|
||||
SOURCE
|
||||
end
|
||||
|
||||
it 'registers an offense and corrects by converting hash to kwarg' do
|
||||
expect_offense(<<~SOURCE, 'create_service.rb')
|
||||
users.call(id, { a: :b, c: :d })
|
||||
^^^^^^^^^^^^^^^^ Using the last argument as keyword parameters is deprecated
|
||||
SOURCE
|
||||
|
||||
expect_correction(<<~SOURCE)
|
||||
users.call(id, a: :b, c: :d)
|
||||
SOURCE
|
||||
end
|
||||
|
||||
it 'registers an offense and corrects by converting splat to double splat' do
|
||||
expect_offense(<<~SOURCE, 'create_service.rb')
|
||||
users.call(id, *params)
|
||||
^^^^^^^ Using the last argument as keyword parameters is deprecated
|
||||
SOURCE
|
||||
|
||||
expect_correction(<<~SOURCE)
|
||||
users.call(id, **params)
|
||||
SOURCE
|
||||
end
|
||||
|
||||
it 'does not register an offense if already a kwarg', :aggregate_failures do
|
||||
expect_no_offenses(<<~SOURCE, 'create_service.rb')
|
||||
users.call(**params)
|
||||
SOURCE
|
||||
|
||||
expect_no_offenses(<<~SOURCE, 'create_service.rb')
|
||||
users.call(id, a: :b, c: :d)
|
||||
SOURCE
|
||||
end
|
||||
|
||||
it 'does not register an offense if the method name does not match' do
|
||||
expect_no_offenses(<<~SOURCE, 'create_service.rb')
|
||||
users.process(params)
|
||||
SOURCE
|
||||
end
|
||||
|
||||
it 'does not register an offense if the line number does not match' do
|
||||
expect_no_offenses(<<~SOURCE, 'create_service.rb')
|
||||
users.process
|
||||
users.call(params)
|
||||
SOURCE
|
||||
end
|
||||
|
||||
it 'does not register an offense if the filename does not match' do
|
||||
expect_no_offenses(<<~SOURCE, 'update_service.rb')
|
||||
users.call(params)
|
||||
SOURCE
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,6 +8,8 @@ if $".include?(File.expand_path('fast_spec_helper.rb', __dir__))
|
|||
abort 'Aborting...'
|
||||
end
|
||||
|
||||
require './spec/deprecation_toolkit_env'
|
||||
|
||||
require './spec/simplecov_env'
|
||||
SimpleCovEnv.start!
|
||||
|
||||
|
|
|
@ -93,13 +93,6 @@ module StubObjectStorage
|
|||
end
|
||||
|
||||
def stub_terraform_state_object_storage(**params)
|
||||
stub_object_storage_uploader(config: Gitlab.config.terraform_state.object_store,
|
||||
uploader: Terraform::VersionedStateUploader,
|
||||
remote_directory: 'terraform',
|
||||
**params)
|
||||
end
|
||||
|
||||
def stub_terraform_state_version_object_storage(**params)
|
||||
stub_object_storage_uploader(config: Gitlab.config.terraform_state.object_store,
|
||||
uploader: Terraform::StateUploader,
|
||||
remote_directory: 'terraform',
|
||||
|
|
|
@ -3,23 +3,45 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Terraform::StateUploader do
|
||||
subject { terraform_state.file }
|
||||
subject { state_version.file }
|
||||
|
||||
let(:terraform_state) { create(:terraform_state, :with_file) }
|
||||
let(:state_version) { create(:terraform_state_version) }
|
||||
|
||||
before do
|
||||
stub_terraform_state_object_storage
|
||||
end
|
||||
|
||||
describe '#filename' do
|
||||
it 'contains the UUID of the terraform state record' do
|
||||
expect(subject.filename).to include(terraform_state.uuid)
|
||||
it 'contains the version of the terraform state record' do
|
||||
expect(subject.filename).to eq("#{state_version.version}.tfstate")
|
||||
end
|
||||
|
||||
context 'legacy state with versioning disabled' do
|
||||
let(:state) { create(:terraform_state, versioning_enabled: false) }
|
||||
let(:state_version) { create(:terraform_state_version, terraform_state: state) }
|
||||
|
||||
it 'contains the UUID of the terraform state record' do
|
||||
expect(subject.filename).to eq("#{state_version.uuid}.tfstate")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#store_dir' do
|
||||
it 'contains the ID of the project' do
|
||||
expect(subject.store_dir).to include(terraform_state.project_id.to_s)
|
||||
it 'hashes the project ID and UUID' do
|
||||
expect(Gitlab::HashedPath).to receive(:new)
|
||||
.with(state_version.uuid, root_hash: state_version.project_id)
|
||||
.and_return(:store_dir)
|
||||
|
||||
expect(subject.store_dir).to eq(:store_dir)
|
||||
end
|
||||
|
||||
context 'legacy state with versioning disabled' do
|
||||
let(:state) { create(:terraform_state, versioning_enabled: false) }
|
||||
let(:state_version) { create(:terraform_state_version, terraform_state: state) }
|
||||
|
||||
it 'contains the ID of the project' do
|
||||
expect(subject.store_dir).to include(state_version.project_id.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -27,7 +49,7 @@ RSpec.describe Terraform::StateUploader do
|
|||
it 'creates a digest with a secret key and the project id' do
|
||||
expect(OpenSSL::HMAC)
|
||||
.to receive(:digest)
|
||||
.with('SHA256', Gitlab::Application.secrets.db_key_base, terraform_state.project_id.to_s)
|
||||
.with('SHA256', Gitlab::Application.secrets.db_key_base, state_version.project_id.to_s)
|
||||
.and_return('digest')
|
||||
|
||||
expect(subject.key).to eq('digest')
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Terraform::VersionedStateUploader do
|
||||
subject { model.file }
|
||||
|
||||
let(:model) { create(:terraform_state_version, :with_file) }
|
||||
|
||||
before do
|
||||
stub_terraform_state_object_storage
|
||||
end
|
||||
|
||||
describe '#filename' do
|
||||
it 'contains the version of the terraform state record' do
|
||||
expect(subject.filename).to eq("#{model.version}.tfstate")
|
||||
end
|
||||
|
||||
context 'legacy state with versioning disabled' do
|
||||
let(:state) { create(:legacy_terraform_state) }
|
||||
let(:model) { create(:terraform_state_version, terraform_state: state) }
|
||||
|
||||
it 'contains the UUID of the terraform state record' do
|
||||
expect(subject.filename).to eq("#{model.uuid}.tfstate")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#store_dir' do
|
||||
it 'hashes the project ID and UUID' do
|
||||
expect(Gitlab::HashedPath).to receive(:new)
|
||||
.with(model.uuid, root_hash: model.project_id)
|
||||
.and_return(:store_dir)
|
||||
|
||||
expect(subject.store_dir).to eq(:store_dir)
|
||||
end
|
||||
|
||||
context 'legacy state with versioning disabled' do
|
||||
let(:state) { create(:legacy_terraform_state) }
|
||||
let(:model) { create(:terraform_state_version, terraform_state: state) }
|
||||
|
||||
it 'contains the ID of the project' do
|
||||
expect(subject.store_dir).to include(model.project_id.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue